From 0648160276cd4a31b21e96950213a6bae0fc03d2 Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Sun, 10 Jul 2016 15:57:47 -0700 Subject: [PATCH 1/4] use role name rather than token displayname in generated mysql usernames If a single token generates multiple myself roles, the generated mysql username was previously prepended with the displayname of the vault user; this makes the output of `show processlist` in mysql potentially difficult to correlate with the roles actually in use without cross- checking against the vault audit log. See https://github.com/hashicorp/vault/pull/1603 for further discussion. --- builtin/logical/mysql/path_role_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/logical/mysql/path_role_create.go b/builtin/logical/mysql/path_role_create.go index 5c10f9c874..4d0553176a 100644 --- a/builtin/logical/mysql/path_role_create.go +++ b/builtin/logical/mysql/path_role_create.go @@ -51,7 +51,7 @@ func (b *backend) pathRoleCreateRead( } // Generate our username and password. MySQL limits user to 16 characters - displayName := req.DisplayName + displayName := name if len(displayName) > 10 { displayName = displayName[:10] } From 417cf49bb702c4cbe3f2fde79382aa59f17f7a8e Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Tue, 12 Jul 2016 17:05:43 -0700 Subject: [PATCH 2/4] allow overriding the default truncation length for mysql usernames see https://github.com/hashicorp/vault/issues/1605 --- builtin/logical/mysql/path_role_create.go | 11 +++++++++-- builtin/logical/mysql/path_roles.go | 17 +++++++++++++++-- website/source/docs/secrets/mysql/index.html.md | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/builtin/logical/mysql/path_role_create.go b/builtin/logical/mysql/path_role_create.go index 4d0553176a..734b34ced3 100644 --- a/builtin/logical/mysql/path_role_create.go +++ b/builtin/logical/mysql/path_role_create.go @@ -31,6 +31,7 @@ func pathRoleCreate(b *backend) *framework.Path { func (b *backend) pathRoleCreateRead( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { name := data.Get("name").(string) + var usernameLength int // Get the role role, err := b.Role(req.Storage, name) @@ -52,8 +53,14 @@ func (b *backend) pathRoleCreateRead( // Generate our username and password. MySQL limits user to 16 characters displayName := name - if len(displayName) > 10 { - displayName = displayName[:10] + ul, ok := data.GetOk("username_length") + if ok == true { + usernameLength = ul.(int) + } else { + usernameLength = 10 + } + if len(displayName) > usernameLength { + displayName = displayName[:usernameLength] } userUUID, err := uuid.GenerateUUID() if err != nil { diff --git a/builtin/logical/mysql/path_roles.go b/builtin/logical/mysql/path_roles.go index d7d621a909..998e2ad2dc 100644 --- a/builtin/logical/mysql/path_roles.go +++ b/builtin/logical/mysql/path_roles.go @@ -34,6 +34,11 @@ func pathRoles(b *backend) *framework.Path { Type: framework.TypeString, Description: "SQL string to create a user. See help for more info.", }, + + "username_length": &framework.FieldSchema{ + Type: framework.TypeInt, + Description: "number of characters to truncate generated mysql usernames to (default 10)", + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -105,6 +110,7 @@ func (b *backend) pathRoleCreate( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { name := data.Get("name").(string) sql := data.Get("sql").(string) + username_length := data.Get("username_length").(int) // Get our connection db, err := b.DB(req.Storage) @@ -127,7 +133,8 @@ func (b *backend) pathRoleCreate( // Store it entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ - SQL: sql, + SQL: sql, + USERNAME_LENGTH: username_length, }) if err != nil { return nil, err @@ -139,7 +146,8 @@ func (b *backend) pathRoleCreate( } type roleEntry struct { - SQL string `json:"sql"` + SQL string `json:"sql"` + USERNAME_LENGTH int `json:"username_length"` } const pathRoleHelpSyn = ` @@ -165,4 +173,9 @@ Example of a decent SQL query to use: Note the above user would be able to access anything in db1. Please see the MySQL manual on the GRANT command to learn how to do more fine grained access. + +The "username_length" parameter determines how many characters of the +role name will be used in creating the generated mysql username; the +default is 10. Note that mysql versions prior to 5.8 have a 16 character +total limit on usernames. ` diff --git a/website/source/docs/secrets/mysql/index.html.md b/website/source/docs/secrets/mysql/index.html.md index 6e0ea36096..bf43f5fd61 100644 --- a/website/source/docs/secrets/mysql/index.html.md +++ b/website/source/docs/secrets/mysql/index.html.md @@ -105,6 +105,13 @@ that trusted operators can manage the role definitions, and both users and applications are restricted in the credentials they are allowed to read. +Optionally, you may configure the number of character from the role +name that are truncated to form the mysql usernamed interpolated into +the `{{name}}` field: the default is 10. Note that versions of +mysql prior to 5.8 have a 16 character total limit on user names, so +it is probably not safe to increase this above the default on versions +prior to that. + ## API ### /mysql/config/connection @@ -234,6 +241,13 @@ allowed to read. Must be semi-colon separated. The '{{name}}' and '{{password}}' values will be substituted. +
  • + username_length + optional + Determines how many characters from the role name will be used + to form the mysql username interpolated into the '{{name}}' field + of the sql parameter. +
  • From 83635c16b6b62d7b43d3d1853a0cb189133f18a6 Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Wed, 20 Jul 2016 06:36:51 -0700 Subject: [PATCH 3/4] respond to feedback from @vishalnayak - split out usernameLength and displaynameLength truncation values, as they are different things - fetch username and displayname lengths from the role, not from the request parameters - add appropriate defaults for username and displayname lengths --- builtin/logical/mysql/path_role_create.go | 19 +++++------- builtin/logical/mysql/path_roles.go | 29 +++++++++++++++---- .../source/docs/secrets/mysql/index.html.md | 28 ++++++++++++------ 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/builtin/logical/mysql/path_role_create.go b/builtin/logical/mysql/path_role_create.go index 734b34ced3..290e1bae63 100644 --- a/builtin/logical/mysql/path_role_create.go +++ b/builtin/logical/mysql/path_role_create.go @@ -31,7 +31,6 @@ func pathRoleCreate(b *backend) *framework.Path { func (b *backend) pathRoleCreateRead( req *logical.Request, data *framework.FieldData) (*logical.Response, error) { name := data.Get("name").(string) - var usernameLength int // Get the role role, err := b.Role(req.Storage, name) @@ -51,24 +50,20 @@ func (b *backend) pathRoleCreateRead( lease = &configLease{} } - // Generate our username and password. MySQL limits user to 16 characters + // Generate our username and password. The username will be the name of + // the role, truncated to role.displaynameLength, appended to a uuid, + // with the entire string truncated to role.usernameLength. displayName := name - ul, ok := data.GetOk("username_length") - if ok == true { - usernameLength = ul.(int) - } else { - usernameLength = 10 - } - if len(displayName) > usernameLength { - displayName = displayName[:usernameLength] + if len(displayName) > role.DisplaynameLength { + displayName = displayName[:role.DisplaynameLength] } userUUID, err := uuid.GenerateUUID() if err != nil { return nil, err } username := fmt.Sprintf("%s-%s", displayName, userUUID) - if len(username) > 16 { - username = username[:16] + if len(username) > role.UsernameLength { + username = username[:role.UsernameLength] } password, err := uuid.GenerateUUID() if err != nil { diff --git a/builtin/logical/mysql/path_roles.go b/builtin/logical/mysql/path_roles.go index 998e2ad2dc..cb34df2b23 100644 --- a/builtin/logical/mysql/path_roles.go +++ b/builtin/logical/mysql/path_roles.go @@ -37,7 +37,14 @@ func pathRoles(b *backend) *framework.Path { "username_length": &framework.FieldSchema{ Type: framework.TypeInt, - Description: "number of characters to truncate generated mysql usernames to (default 10)", + Description: "number of characters to truncate generated mysql usernames to (default 16)", + Default: 16, + }, + + "displayname_length": &framework.FieldSchema{ + Type: framework.TypeInt, + Description: "number of characters to truncate the rolename portion of generated mysql usernames to (default 10)", + Default: 10, }, }, @@ -111,6 +118,7 @@ func (b *backend) pathRoleCreate( name := data.Get("name").(string) sql := data.Get("sql").(string) username_length := data.Get("username_length").(int) + displayname_length := data.Get("displayname_length").(int) // Get our connection db, err := b.DB(req.Storage) @@ -133,8 +141,9 @@ func (b *backend) pathRoleCreate( // Store it entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ - SQL: sql, - USERNAME_LENGTH: username_length, + SQL: sql, + UsernameLength: username_length, + DisplaynameLength: displayname_length, }) if err != nil { return nil, err @@ -146,8 +155,9 @@ func (b *backend) pathRoleCreate( } type roleEntry struct { - SQL string `json:"sql"` - USERNAME_LENGTH int `json:"username_length"` + SQL string `json:"sql"` + UsernameLength int `json:"username_length"` + DisplaynameLength int `json:"displayname_length"` } const pathRoleHelpSyn = ` @@ -174,8 +184,15 @@ Example of a decent SQL query to use: Note the above user would be able to access anything in db1. Please see the MySQL manual on the GRANT command to learn how to do more fine grained access. -The "username_length" parameter determines how many characters of the +The "displayname_length" parameter determines how many characters of the role name will be used in creating the generated mysql username; the default is 10. Note that mysql versions prior to 5.8 have a 16 character total limit on usernames. + +The "username_length" parameter determines how many total characters the +generated username (including both the displayname and the uuid portion) will +be truncated to. Versions of MySQL prior to 5.7.8 are limited to 16 +characters total (see http://dev.mysql.com/doc/refman/5.7/en/user-names.html) +so that is the default; for versions >=5.7.8 it is safe to increase this +to 32. ` diff --git a/website/source/docs/secrets/mysql/index.html.md b/website/source/docs/secrets/mysql/index.html.md index bf43f5fd61..96b62b2b8c 100644 --- a/website/source/docs/secrets/mysql/index.html.md +++ b/website/source/docs/secrets/mysql/index.html.md @@ -92,7 +92,7 @@ Key Value lease_id mysql/creds/readonly/bd404e98-0f35-b378-269a-b7770ef01897 lease_duration 3600 password 132ae3ef-5a64-7499-351e-bfe59f3a2a21 -username root-aefa635a-18 +username readonly-aefa635a-18 ``` By reading from the `creds/readonly` path, Vault has generated a new @@ -105,12 +105,15 @@ that trusted operators can manage the role definitions, and both users and applications are restricted in the credentials they are allowed to read. -Optionally, you may configure the number of character from the role -name that are truncated to form the mysql usernamed interpolated into -the `{{name}}` field: the default is 10. Note that versions of -mysql prior to 5.8 have a 16 character total limit on user names, so -it is probably not safe to increase this above the default on versions -prior to that. +Optionally, you may configure both the number of characters from the role name +that are truncated to form the display name portion of the mysql username +interpolated into the `{{name}}` field: the default is 10. + +You may also configure the total number of characters allowed in the entire +generated username (the sum of the display name and uuid poritions); the +default is 16. Note that versions of MySQL prior to 5.8 have a 16 character +total limit on user names, so it is probably not safe to increase this above +the default on versions prior to that. ## API @@ -242,12 +245,19 @@ prior to that. values will be substituted.
  • - username_length + displayname_length optional Determines how many characters from the role name will be used to form the mysql username interpolated into the '{{name}}' field of the sql parameter.
  • +
  • + username_length + optional + Determines the maximum total length in characters of the + mysql username interpolated into the '{{name}}' field + of the sql parameter. +
  • @@ -379,7 +389,7 @@ prior to that. ```javascript { "data": { - "username": "root-aefa635a-18", + "username": "rolename-aefa635a-18", "password": "132ae3ef-5a64-7499-351e-bfe59f3a2a21" } } From e824f6040b81a321f860480541abf7eabe21f326 Mon Sep 17 00:00:00 2001 From: "Nathan J. Mehl" Date: Wed, 20 Jul 2016 10:17:00 -0700 Subject: [PATCH 4/4] use both role name and token display name to form mysql username --- builtin/logical/mysql/path_role_create.go | 21 +++++++--- builtin/logical/mysql/path_roles.go | 39 +++++++++++++------ .../source/docs/secrets/mysql/index.html.md | 15 +++++-- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/builtin/logical/mysql/path_role_create.go b/builtin/logical/mysql/path_role_create.go index 290e1bae63..fa2529ff75 100644 --- a/builtin/logical/mysql/path_role_create.go +++ b/builtin/logical/mysql/path_role_create.go @@ -50,10 +50,21 @@ func (b *backend) pathRoleCreateRead( lease = &configLease{} } - // Generate our username and password. The username will be the name of - // the role, truncated to role.displaynameLength, appended to a uuid, - // with the entire string truncated to role.usernameLength. - displayName := name + // Generate our username and password. The username will be a + // concatenation of: + // + // - the role name, truncated to role.rolenameLength (default 4) + // - the token display name, truncated to role.displaynameLength (default 4) + // - a UUID + // + // the entire contactenated string is then truncated to role.usernameLength, + // which by default is 16 due to limitations in older but still-prevalant + // versions of MySQL. + roleName := name + if len(roleName) > role.RolenameLength { + roleName = roleName[:role.RolenameLength] + } + displayName := req.DisplayName if len(displayName) > role.DisplaynameLength { displayName = displayName[:role.DisplaynameLength] } @@ -61,7 +72,7 @@ func (b *backend) pathRoleCreateRead( if err != nil { return nil, err } - username := fmt.Sprintf("%s-%s", displayName, userUUID) + username := fmt.Sprintf("%s-%s-%s", roleName, displayName, userUUID) if len(username) > role.UsernameLength { username = username[:role.UsernameLength] } diff --git a/builtin/logical/mysql/path_roles.go b/builtin/logical/mysql/path_roles.go index cb34df2b23..813ad5df77 100644 --- a/builtin/logical/mysql/path_roles.go +++ b/builtin/logical/mysql/path_roles.go @@ -41,10 +41,16 @@ func pathRoles(b *backend) *framework.Path { Default: 16, }, + "rolename_length": &framework.FieldSchema{ + Type: framework.TypeInt, + Description: "number of characters to truncate the rolename portion of generated mysql usernames to (default 4)", + Default: 4, + }, + "displayname_length": &framework.FieldSchema{ Type: framework.TypeInt, - Description: "number of characters to truncate the rolename portion of generated mysql usernames to (default 10)", - Default: 10, + Description: "number of characters to truncate the displayname portion of generated mysql usernames to (default 4)", + Default: 4, }, }, @@ -118,6 +124,7 @@ func (b *backend) pathRoleCreate( name := data.Get("name").(string) sql := data.Get("sql").(string) username_length := data.Get("username_length").(int) + rolename_length := data.Get("rolename_length").(int) displayname_length := data.Get("displayname_length").(int) // Get our connection @@ -144,6 +151,7 @@ func (b *backend) pathRoleCreate( SQL: sql, UsernameLength: username_length, DisplaynameLength: displayname_length, + RolenameLength: rolename_length, }) if err != nil { return nil, err @@ -158,6 +166,7 @@ type roleEntry struct { SQL string `json:"sql"` UsernameLength int `json:"username_length"` DisplaynameLength int `json:"displayname_length"` + RolenameLength int `json:"rolename_length"` } const pathRoleHelpSyn = ` @@ -184,15 +193,23 @@ Example of a decent SQL query to use: Note the above user would be able to access anything in db1. Please see the MySQL manual on the GRANT command to learn how to do more fine grained access. -The "displayname_length" parameter determines how many characters of the -role name will be used in creating the generated mysql username; the -default is 10. Note that mysql versions prior to 5.8 have a 16 character -total limit on usernames. +The "rolename_length" parameter determines how many characters of the role name +will be used in creating the generated mysql username; the default is 4. + +The "displayname_length" parameter determines how many characters of the token +display name will be used in creating the generated mysql username; the default +is 4. The "username_length" parameter determines how many total characters the -generated username (including both the displayname and the uuid portion) will -be truncated to. Versions of MySQL prior to 5.7.8 are limited to 16 -characters total (see http://dev.mysql.com/doc/refman/5.7/en/user-names.html) -so that is the default; for versions >=5.7.8 it is safe to increase this -to 32. +generated username (including the role name, token display name and the uuid +portion) will be truncated to. Versions of MySQL prior to 5.7.8 are limited to +16 characters total (see +http://dev.mysql.com/doc/refman/5.7/en/user-names.html) so that is the default; +for versions >=5.7.8 it is safe to increase this to 32. + +For best readability in MySQL process lists, we recommend using MySQL 5.7.8 or +later, setting "username_length" to 32 and setting both "rolename_length" and +"displayname_length" to 8. However due the the prevalence of older versions of +MySQL in general deployment, the defaults are currently tuned for a +username_length of 16. ` diff --git a/website/source/docs/secrets/mysql/index.html.md b/website/source/docs/secrets/mysql/index.html.md index 96b62b2b8c..0f20ae50bc 100644 --- a/website/source/docs/secrets/mysql/index.html.md +++ b/website/source/docs/secrets/mysql/index.html.md @@ -245,18 +245,25 @@ the default on versions prior to that. values will be substituted.
  • - displayname_length + rolename_length optional Determines how many characters from the role name will be used to form the mysql username interpolated into the '{{name}}' field - of the sql parameter. + of the sql parameter. The default is 4. +
  • +
  • + displayname_length + optional + Determines how many characters from the token display name will be used + to form the mysql username interpolated into the '{{name}}' field + of the sql parameter. The default is 4.
  • username_length optional Determines the maximum total length in characters of the mysql username interpolated into the '{{name}}' field - of the sql parameter. + of the sql parameter. The default is 16.
  • @@ -389,7 +396,7 @@ the default on versions prior to that. ```javascript { "data": { - "username": "rolename-aefa635a-18", + "username": "user-role-aefa63", "password": "132ae3ef-5a64-7499-351e-bfe59f3a2a21" } }