diff --git a/builtin/logical/mysql/path_role_create.go b/builtin/logical/mysql/path_role_create.go index 5c10f9c874..fa2529ff75 100644 --- a/builtin/logical/mysql/path_role_create.go +++ b/builtin/logical/mysql/path_role_create.go @@ -50,18 +50,31 @@ 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 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) > 10 { - displayName = displayName[:10] + 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] + username := fmt.Sprintf("%s-%s-%s", roleName, displayName, userUUID) + 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 d7d621a909..813ad5df77 100644 --- a/builtin/logical/mysql/path_roles.go +++ b/builtin/logical/mysql/path_roles.go @@ -34,6 +34,24 @@ 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 16)", + 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 displayname portion of generated mysql usernames to (default 4)", + Default: 4, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -105,6 +123,9 @@ 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) + rolename_length := data.Get("rolename_length").(int) + displayname_length := data.Get("displayname_length").(int) // Get our connection db, err := b.DB(req.Storage) @@ -127,7 +148,10 @@ func (b *backend) pathRoleCreate( // Store it entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ - SQL: sql, + SQL: sql, + UsernameLength: username_length, + DisplaynameLength: displayname_length, + RolenameLength: rolename_length, }) if err != nil { return nil, err @@ -139,7 +163,10 @@ func (b *backend) pathRoleCreate( } type roleEntry struct { - SQL string `json:"sql"` + SQL string `json:"sql"` + UsernameLength int `json:"username_length"` + DisplaynameLength int `json:"displayname_length"` + RolenameLength int `json:"rolename_length"` } const pathRoleHelpSyn = ` @@ -165,4 +192,24 @@ 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 "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 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 6e0ea36096..0f20ae50bc 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,6 +105,16 @@ 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 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 ### /mysql/config/connection @@ -234,6 +244,27 @@ allowed to read. Must be semi-colon separated. The '{{name}}' and '{{password}}' values will be substituted. +
  • + 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. 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. The default is 16. +
  • @@ -365,7 +396,7 @@ allowed to read. ```javascript { "data": { - "username": "root-aefa635a-18", + "username": "user-role-aefa63", "password": "132ae3ef-5a64-7499-351e-bfe59f3a2a21" } }