package cassandra import ( "context" "fmt" "github.com/hashicorp/go-secure-stdlib/tlsutil" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" ) func pathConfigConnection(b *backend) *framework.Path { return &framework.Path{ Pattern: "config/connection", Fields: map[string]*framework.FieldSchema{ "hosts": { Type: framework.TypeString, Description: "Comma-separated list of hosts", }, "username": { Type: framework.TypeString, Description: "The username to use for connecting to the cluster", }, "password": { Type: framework.TypeString, Description: "The password to use for connecting to the cluster", }, "tls": { Type: framework.TypeBool, Description: `Whether to use TLS. If pem_bundle or pem_json are set, this is automatically set to true`, }, "insecure_tls": { Type: framework.TypeBool, Description: `Whether to use TLS but skip verification; has no effect if a CA certificate is provided`, }, // TLS 1.3 is not supported as this engine is deprecated. Please switch to the Cassandra database secrets engine "tls_min_version": { Type: framework.TypeString, Default: "tls12", Description: "Minimum TLS version to use. Accepted values are 'tls10', 'tls11' or 'tls12'. Defaults to 'tls12'", }, "pem_bundle": { Type: framework.TypeString, Description: `PEM-format, concatenated unencrypted secret key and certificate, with optional CA certificate`, }, "pem_json": { Type: framework.TypeString, Description: `JSON containing a PEM-format, unencrypted secret key and certificate, with optional CA certificate. The JSON output of a certificate issued with the PKI backend can be directly passed into this parameter. If both this and "pem_bundle" are specified, this will take precedence.`, }, "protocol_version": { Type: framework.TypeInt, Description: `The protocol version to use. Defaults to 2.`, }, "connect_timeout": { Type: framework.TypeDurationSecond, Default: 5, Description: `The connection timeout to use. Defaults to 5.`, }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ logical.ReadOperation: b.pathConnectionRead, logical.UpdateOperation: b.pathConnectionWrite, }, HelpSynopsis: pathConfigConnectionHelpSyn, HelpDescription: pathConfigConnectionHelpDesc, } } func (b *backend) pathConnectionRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { entry, err := req.Storage.Get(ctx, "config/connection") if err != nil { return nil, err } if entry == nil { return logical.ErrorResponse(fmt.Sprintf("Configure the DB connection with config/connection first")), nil } config := &sessionConfig{} if err := entry.DecodeJSON(config); err != nil { return nil, err } resp := &logical.Response{ Data: map[string]interface{}{ "hosts": config.Hosts, "username": config.Username, "tls": config.TLS, "insecure_tls": config.InsecureTLS, "certificate": config.Certificate, "issuing_ca": config.IssuingCA, "protocol_version": config.ProtocolVersion, "connect_timeout": config.ConnectTimeout, "tls_min_version": config.TLSMinVersion, }, } return resp, nil } func (b *backend) pathConnectionWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { hosts := data.Get("hosts").(string) username := data.Get("username").(string) password := data.Get("password").(string) switch { case len(hosts) == 0: return logical.ErrorResponse("Hosts cannot be empty"), nil case len(username) == 0: return logical.ErrorResponse("Username cannot be empty"), nil case len(password) == 0: return logical.ErrorResponse("Password cannot be empty"), nil } config := &sessionConfig{ Hosts: hosts, Username: username, Password: password, TLS: data.Get("tls").(bool), InsecureTLS: data.Get("insecure_tls").(bool), ProtocolVersion: data.Get("protocol_version").(int), ConnectTimeout: data.Get("connect_timeout").(int), } config.TLSMinVersion = data.Get("tls_min_version").(string) if config.TLSMinVersion == "" { return logical.ErrorResponse("failed to get 'tls_min_version' value"), nil } var ok bool _, ok = tlsutil.TLSLookup[config.TLSMinVersion] if !ok { return logical.ErrorResponse("invalid 'tls_min_version'"), nil } if config.InsecureTLS { config.TLS = true } pemBundle := data.Get("pem_bundle").(string) pemJSON := data.Get("pem_json").(string) var certBundle *certutil.CertBundle var parsedCertBundle *certutil.ParsedCertBundle var err error switch { case len(pemJSON) != 0: parsedCertBundle, err = certutil.ParsePKIJSON([]byte(pemJSON)) if err != nil { return logical.ErrorResponse(fmt.Sprintf("Could not parse given JSON; it must be in the format of the output of the PKI backend certificate issuing command: %s", err)), nil } certBundle, err = parsedCertBundle.ToCertBundle() if err != nil { return logical.ErrorResponse(fmt.Sprintf("Error marshaling PEM information: %s", err)), nil } config.Certificate = certBundle.Certificate config.PrivateKey = certBundle.PrivateKey config.IssuingCA = certBundle.IssuingCA config.TLS = true case len(pemBundle) != 0: parsedCertBundle, err = certutil.ParsePEMBundle(pemBundle) if err != nil { return logical.ErrorResponse(fmt.Sprintf("Error parsing the given PEM information: %s", err)), nil } certBundle, err = parsedCertBundle.ToCertBundle() if err != nil { return logical.ErrorResponse(fmt.Sprintf("Error marshaling PEM information: %s", err)), nil } config.Certificate = certBundle.Certificate config.PrivateKey = certBundle.PrivateKey config.IssuingCA = certBundle.IssuingCA config.TLS = true } session, err := createSession(config, req.Storage) if err != nil { return logical.ErrorResponse(err.Error()), nil } // Store it entry, err := logical.StorageEntryJSON("config/connection", config) if err != nil { return nil, err } if err := req.Storage.Put(ctx, entry); err != nil { return nil, err } // Reset the DB connection b.ResetDB(session) return nil, nil } const pathConfigConnectionHelpSyn = ` Configure the connection information to talk to Cassandra. ` const pathConfigConnectionHelpDesc = ` This path configures the connection information used to connect to Cassandra. "hosts" is a comma-delimited list of hostnames in the Cassandra cluster. "username" and "password" are self-explanatory, although the given user must have superuser access within Cassandra. Note that since this backend issues username/password credentials, Cassandra must be configured to use PasswordAuthenticator or a similar backend for its authentication. If you wish to have no authorization in Cassandra and want to use TLS client certificates, see the PKI backend. TLS works as follows: * If "tls" is set to true, the connection will use TLS; this happens automatically if "pem_bundle", "pem_json", or "insecure_tls" is set * If "insecure_tls" is set to true, the connection will not perform verification of the server certificate; this also sets "tls" to true * If only "issuing_ca" is set in "pem_json", or the only certificate in "pem_bundle" is a CA certificate, the given CA certificate will be used for server certificate verification; otherwise the system CA certificates will be used * If "certificate" and "private_key" are set in "pem_bundle" or "pem_json", client auth will be turned on for the connection "pem_bundle" should be a PEM-concatenated bundle of a private key + client certificate, an issuing CA certificate, or both. "pem_json" should contain the same information; for convenience, the JSON format is the same as that output by the issue command from the PKI backend. When configuring the connection information, the backend will verify its validity. `