Skip to content
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

Merged
merged 162 commits into from
May 4, 2017
Merged

Combined Database Backend with Plugins #2200

merged 162 commits into from
May 4, 2017

Conversation

briankassouf
Copy link
Contributor

@briankassouf briankassouf commented Dec 20, 2016

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:

  • builtin/logical/database/backend.go
  • builtin/logical/database/backend_test.go
  • builtin/logical/database/dbplugin/client.go
  • builtin/logical/database/dbplugin/databasemiddleware.go
  • builtin/logical/database/dbplugin/plugin.go
  • builtin/logical/database/dbplugin/plugin_test.go
  • builtin/logical/database/dbplugin/server.go
  • builtin/logical/database/path_config_connection.go
  • builtin/logical/database/path_creds_create.go
  • builtin/logical/database/path_roles.go
  • builtin/logical/database/secret_creds.go
  • cli/commands.go
  • command/plugin_exec.go
  • command/server.go
  • command/server/config.go
  • helper/builtinplugins/builtin.go
  • helper/pluginutil/runner.go
  • helper/pluginutil/tls.go
  • helper/strutil/strutil.go
  • logical/system_view.go
  • plugins/database/mssql/mssql-database-plugin/main.go
  • plugins/database/mssql/mssql.go
  • plugins/database/mssql/mssql_test.go
  • plugins/database/mysql/mysql-database-plugin/main.go
  • plugins/database/mysql/mysql.go
  • plugins/database/mysql/mysql_test.go
  • plugins/database/postgresql/postgresql-database-plugin/main.go
  • plugins/database/postgresql/postgresql.go
  • plugins/database/postgresql/postgresql_test.go
  • plugins/helper/database/connutil/cassandra.go
  • plugins/helper/database/connutil/connutil.go
  • plugins/helper/database/connutil/sql.go
  • plugins/helper/database/credsutil/cassandra.go
  • plugins/helper/database/credsutil/credsutil.go
  • plugins/helper/database/credsutil/sql.go
  • plugins/helper/database/dbutil/dbutil.go
  • vault/core.go
  • vault/dynamic_system_view.go
  • vault/logical_system.go
  • vault/logical_system_test.go
  • vault/plugin_catalog.go
  • vault/plugin_catalog_test.go
  • vault/testing.go

sync.RWMutex
}

// resetAllDBs closes all connections from all database types
Copy link
Contributor

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

}

func (b *databaseBackend) Role(s logical.Storage, n string) (*roleEntry, error) {
entry, err := s.Get("role/" + n)
Copy link
Contributor

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 {
Copy link
Contributor

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/endpoing/endpoint/

const backendHelp = `
The database backend supports using many different databases
as secret backends, including but not limited to:
cassandra, msslq, mysql, postgres
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/msslq/mssql/


// 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) {
Copy link
Contributor

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.

x509.ExtKeyUsageServerAuth,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
SerialNumber: big.NewInt(mathrand.Int63()),
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch!

Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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!

unwrapToken := os.Getenv(PluginUnwrapTokenEnv)

// Ensure unwrap token is a JWT
if strings.Count(unwrapToken, ".") != 2 {
Copy link
Contributor

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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 :)

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor

@calvn calvn left a 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.

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)
Copy link
Contributor

@calvn calvn May 4, 2017

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/SQL statements/Statements/

// 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
Copy link
Contributor

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())
Copy link
Contributor

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)

Copy link
Contributor Author

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!

// 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),
Copy link

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.

Copy link
Member

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?

Copy link
Contributor

@chrishoffman chrishoffman left a 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!

Copy link
Member

@jefferai jefferai left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants