-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Combined Database Backend with Plugins #2200
Conversation
0ac09a4
to
a80d67d
Compare
…ate a new database instance with an updated config
9457346
to
4d33509
Compare
builtin/logical/database/backend.go
Outdated
sync.RWMutex | ||
} | ||
|
||
// resetAllDBs closes all connections from all database types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we don't lint but s/resetAllDBs/closeAllDBs
builtin/logical/database/backend.go
Outdated
} | ||
|
||
func (b *databaseBackend) Role(s logical.Storage, n string) (*roleEntry, error) { | ||
entry, err := s.Get("role/" + n) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you update n
to something like name
or roleName
? It looks like name
is used to describe the database name so roleName
may make more sense?!?
} | ||
|
||
// pathConnectionRead reads out the connection configuration | ||
func (b *databaseBackend) connectionReadHandler() framework.OperationFunc { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/pathConnectionRead/connectionReadHandler/
for a full list of accepted connection details. | ||
|
||
In addition to the database specific connection details, this endpoing also | ||
accepts: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/endpoing/endpoint/
builtin/logical/database/backend.go
Outdated
const backendHelp = ` | ||
The database backend supports using many different databases | ||
as secret backends, including but not limited to: | ||
cassandra, msslq, mysql, postgres |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/msslq/mssql/
helper/pluginutil/tls.go
Outdated
|
||
// generateSignedCert is used internally to create certificates for the plugin | ||
// client and server. These certs are signed by the given CA Cert and Key. | ||
func GenerateCert() ([]byte, *ecdsa.PrivateKey, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like many of the methods in this file should all be private.
helper/pluginutil/tls.go
Outdated
x509.ExtKeyUsageServerAuth, | ||
}, | ||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement, | ||
SerialNumber: big.NewInt(mathrand.Int63()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use certutil.GenerateSerialNumber() here. This is actually not random unless it is seeded. This is all internal but we should probably generate unique certificate serials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bk actually asked me about that a couple of days ago; since each plugin gets a unique cert and serial numbers must be unique for each CA (but only per CA) this isn't an issue, but that said, randomizing is totally fine too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the uniqueness only matters if you have a CRL, but I could be wrong. I don't think this CA implementation is keeping a database so I'm not sure what duplicate serials would do. Anyway, I believe if you restart your vault instance you will start your serials over again in the same sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no...in theory you should be able to identify any certificate uniquely via authority ID and serial, for whatever uses you may have. CRLs only take serials into account but that mostly means if you have duplicates you may block more than one rather than just one cert, so not a security hole really.
You are correct that if you restarted Vault you'd get the same sequence but the CA cert/key would be different, hence the authority ID would be different, so it doesn't really matter. No harm to randomize though!
helper/pluginutil/tls.go
Outdated
unwrapToken := os.Getenv(PluginUnwrapTokenEnv) | ||
|
||
// Ensure unwrap token is a JWT | ||
if strings.Count(unwrapToken, ".") != 2 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check as one of the first checks in jwt.ParseJWT
. The error is a little different but it is a constant so you could switch on it.
@@ -31,6 +31,19 @@ func StrListSubset(super, sub []string) bool { | |||
|
|||
// Parses a comma separated list of strings into a slice of strings. | |||
// The return slice will be sorted and will not contain duplicate or | |||
// empty items. | |||
func ParseDedupAndSortStrings(input string, sep string) []string { | |||
input = strings.TrimSpace(input) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this came back with one of the commits. This was renamed to ParseDedupLowercaseAndSortStrings
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually added this function! I didn't want it to lowercase :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, I scanned them and they looked identical. I missed that bool flag.
The full list of configurable options can be seen in the [MSSQL database | ||
plugin API](/api/secret/databases/mssql.html) page. | ||
|
||
Or for more information on the Database secret backend's HTTP API please see the [Database secret |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "Or" could probably be removed from this sentence in all these plugin docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of small items.
builtin/logical/database/backend.go
Outdated
func (b *databaseBackend) DatabaseConfig(s logical.Storage, name string) (*DatabaseConfig, error) { | ||
entry, err := s.Get(fmt.Sprintf("config/%s", name)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read connection configuration with name: %s", name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should err
be appended and returned back as well?
}, | ||
"rollback_statements": { | ||
Type: framework.TypeString, | ||
Description: `SQL statements to be executed to revoke a user. Must be a semicolon-separated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/SQL statements/Statements/
logical/system_view.go
Outdated
// name. Returns a PluginRunner or an error if a plugin can not be found. | ||
LookupPlugin(string) (*pluginutil.PluginRunner, error) | ||
|
||
// MlockEnabled returns the configuration setting for Enableing mlock on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Enableing/enabling/
if err != nil { | ||
return "", err | ||
} | ||
username := fmt.Sprintf("vault_%s_%s_%d", displayName, userUUID, time.Now().Unix()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this username generation different from that of SQLCredentialsProducer?
username := fmt.Sprintf("v-%s-%s", displayName, userUUID)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep it consistent with how the other cassandra backend has been doing it!
helper/builtinplugins/builtin.go
Outdated
// different username settings passed by the constructor. | ||
"mysql-database-plugin": mysql.New(mysql.DisplayNameLen, mysql.UsernameLen), | ||
"aurora-database-plugin": mysql.New(mysql.LegacyDisplayNameLen, mysql.LegacyUsernameLen), | ||
"rds-database-plugin": mysql.New(mysql.LegacyDisplayNameLen, mysql.LegacyUsernameLen), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both RDS and Aurora have PostgreSQL flavors. It may be prudent to include mysql
in the names here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. It looks like that got added in November 2016, long after the original RFC for this. That's a good catch, and maybe we should just add some variants for those up-front, even if (like here) they are the same to start out with. @briankassouf what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A thousand 🚢s and a million 🌈s, sir.
This PR introduces a new dynamic secrets backend that combines the functionality in the existing database backends and allows for more database types to be added through plugins. It uses hashicorp/go-plugin.
Plugins in this backend can be run in one of two ways: builtin vs external. Builtin plugins ship with the vault binary and are run as a subcommand of vault. External plugins can be placed in a configured plugin directory on the server.
External plugins must be registered to vault's plugin catalog before they can be run by a backend. The catalog requires a plugin to live in the configured plugin directory and a SHA-256 checksum matching the binary must be provided.
Once a plugin is registered to vault, or if it's built in, it can be configured through the database backend and used to create credentials for the database configuration.
======= Jeff's review list: