// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package pki import ( "context" "net/http" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) func pathConfigCA(b *backend) *framework.Path { return &framework.Path{ Pattern: "config/ca", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, OperationVerb: "configure", OperationSuffix: "ca", }, Fields: map[string]*framework.FieldSchema{ "pem_bundle": { Type: framework.TypeString, Description: `PEM-format, concatenated unencrypted secret key and certificate.`, }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathImportIssuers, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "mapping": { Type: framework.TypeMap, Description: "A mapping of issuer_id to key_id for all issuers included in this request", Required: true, }, "imported_keys": { Type: framework.TypeCommaStringSlice, Description: "Net-new keys imported as a part of this request", Required: true, }, "imported_issuers": { Type: framework.TypeCommaStringSlice, Description: "Net-new issuers imported as a part of this request", Required: true, }, "existing_keys": { Type: framework.TypeCommaStringSlice, Description: "Existing keys specified as part of the import bundle of this request", Required: true, }, "existing_issuers": { Type: framework.TypeCommaStringSlice, Description: "Existing issuers specified as part of the import bundle of this request", Required: true, }, }, }}, }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, }, HelpSynopsis: pathConfigCAHelpSyn, HelpDescription: pathConfigCAHelpDesc, } } const pathConfigCAHelpSyn = ` Set the CA certificate and private key used for generated credentials. ` const pathConfigCAHelpDesc = ` This sets the CA information used for credentials generated by this by this mount. This must be a PEM-format, concatenated unencrypted secret key and certificate. For security reasons, the secret key cannot be retrieved later. ` func pathConfigIssuers(b *backend) *framework.Path { return &framework.Path{ Pattern: "config/issuers", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, }, Fields: map[string]*framework.FieldSchema{ defaultRef: { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, }, "default_follows_latest_issuer": { Type: framework.TypeBool, Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, Default: false, }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.pathCAIssuersRead, DisplayAttrs: &framework.DisplayAttributes{ OperationSuffix: "issuers-configuration", }, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, Required: true, }, "default_follows_latest_issuer": { Type: framework.TypeBool, Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, Required: true, }, }, }}, }, }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCAIssuersWrite, DisplayAttrs: &framework.DisplayAttributes{ OperationVerb: "configure", OperationSuffix: "issuers", }, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, }, "default_follows_latest_issuer": { Type: framework.TypeBool, Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, }, }, }}, }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, }, HelpSynopsis: pathConfigIssuersHelpSyn, HelpDescription: pathConfigIssuersHelpDesc, } } func pathReplaceRoot(b *backend) *framework.Path { return &framework.Path{ Pattern: "root/replace", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, OperationVerb: "replace", OperationSuffix: "root", }, Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, Default: "next", }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathCAIssuersWrite, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, Required: true, }, "default_follows_latest_issuer": { Type: framework.TypeBool, Description: `Whether the default issuer should automatically follow the latest generated or imported issuer. Defaults to false.`, Required: true, }, }, }}, }, // Read more about why these flags are set in backend.go. ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, }, HelpSynopsis: pathConfigIssuersHelpSyn, HelpDescription: pathConfigIssuersHelpDesc, } } func (b *backend) pathCAIssuersRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Cannot read defaults until migration has completed"), nil } sc := b.makeStorageContext(ctx, req.Storage) config, err := sc.getIssuersConfig() if err != nil { return logical.ErrorResponse("Error loading issuers configuration: " + err.Error()), nil } return b.formatCAIssuerConfigRead(config), nil } func (b *backend) formatCAIssuerConfigRead(config *issuerConfigEntry) *logical.Response { return &logical.Response{ Data: map[string]interface{}{ defaultRef: config.DefaultIssuerId, "default_follows_latest_issuer": config.DefaultFollowsLatestIssuer, }, } } func (b *backend) pathCAIssuersWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { // Since we're planning on updating issuers here, grab the lock so we've // got a consistent view. b.issuersLock.Lock() defer b.issuersLock.Unlock() if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Cannot update defaults until migration has completed"), nil } sc := b.makeStorageContext(ctx, req.Storage) // Validate the new default reference. newDefault := data.Get(defaultRef).(string) if len(newDefault) == 0 || newDefault == defaultRef { return logical.ErrorResponse("Invalid issuer specification; must be non-empty and can't be 'default'."), nil } parsedIssuer, err := sc.resolveIssuerReference(newDefault) if err != nil { return logical.ErrorResponse("Error resolving issuer reference: " + err.Error()), nil } entry, err := sc.fetchIssuerById(parsedIssuer) if err != nil { return logical.ErrorResponse("Unable to fetch issuer: " + err.Error()), nil } // Get the other new parameters. This doesn't exist on the /root/replace // variant of this call. var followIssuer bool followIssuersRaw, followOk := data.GetOk("default_follows_latest_issuer") if followOk { followIssuer = followIssuersRaw.(bool) } // Update the config config, err := sc.getIssuersConfig() if err != nil { return logical.ErrorResponse("Unable to fetch existing issuers configuration: " + err.Error()), nil } config.DefaultIssuerId = parsedIssuer if followOk { config.DefaultFollowsLatestIssuer = followIssuer } // Add our warning if necessary. response := b.formatCAIssuerConfigRead(config) if len(entry.KeyID) == 0 { msg := "This selected default issuer has no key associated with it. Some operations like issuing certificates and signing CRLs will be unavailable with the requested default issuer until a key is imported or the default issuer is changed." response.AddWarning(msg) b.Logger().Error(msg) } if err := sc.setIssuersConfig(config); err != nil { return logical.ErrorResponse("Error updating issuer configuration: " + err.Error()), nil } return response, nil } const pathConfigIssuersHelpSyn = `Read and set the default issuer certificate for signing.` const pathConfigIssuersHelpDesc = ` This path allows configuration of issuer parameters. Presently, the "default" parameter controls which issuer is the default, accessible by the existing signing paths (/root/sign-intermediate, /root/sign-self-issued, /sign-verbatim, /sign/:role, and /issue/:role). The /root/replace path is aliased to this path, with default taking the value of the issuer with the name "next", if it exists. ` func pathConfigKeys(b *backend) *framework.Path { return &framework.Path{ Pattern: "config/keys", DisplayAttrs: &framework.DisplayAttributes{ OperationPrefix: operationPrefixPKI, }, Fields: map[string]*framework.FieldSchema{ defaultRef: { Type: framework.TypeString, Description: `Reference (name or identifier) of the default key.`, }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathKeyDefaultWrite, DisplayAttrs: &framework.DisplayAttributes{ OperationVerb: "configure", OperationSuffix: "keys", }, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, Required: true, }, }, }}, }, ForwardPerformanceStandby: true, ForwardPerformanceSecondary: true, }, logical.ReadOperation: &framework.PathOperation{ Callback: b.pathKeyDefaultRead, DisplayAttrs: &framework.DisplayAttributes{ OperationSuffix: "keys-configuration", }, Responses: map[int][]framework.Response{ http.StatusOK: {{ Description: "OK", Fields: map[string]*framework.FieldSchema{ "default": { Type: framework.TypeString, Description: `Reference (name or identifier) to the default issuer.`, }, }, }}, }, ForwardPerformanceStandby: false, ForwardPerformanceSecondary: false, }, }, HelpSynopsis: pathConfigKeysHelpSyn, HelpDescription: pathConfigKeysHelpDesc, } } func (b *backend) pathKeyDefaultRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Cannot read key defaults until migration has completed"), nil } sc := b.makeStorageContext(ctx, req.Storage) config, err := sc.getKeysConfig() if err != nil { return logical.ErrorResponse("Error loading keys configuration: " + err.Error()), nil } return &logical.Response{ Data: map[string]interface{}{ defaultRef: config.DefaultKeyId, }, }, nil } func (b *backend) pathKeyDefaultWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { // Since we're planning on updating keys here, grab the lock so we've // got a consistent view. b.issuersLock.Lock() defer b.issuersLock.Unlock() if b.useLegacyBundleCaStorage() { return logical.ErrorResponse("Cannot update key defaults until migration has completed"), nil } newDefault := data.Get(defaultRef).(string) if len(newDefault) == 0 || newDefault == defaultRef { return logical.ErrorResponse("Invalid key specification; must be non-empty and can't be 'default'."), nil } sc := b.makeStorageContext(ctx, req.Storage) parsedKey, err := sc.resolveKeyReference(newDefault) if err != nil { return logical.ErrorResponse("Error resolving issuer reference: " + err.Error()), nil } err = sc.updateDefaultKeyId(parsedKey) if err != nil { return logical.ErrorResponse("Error updating issuer configuration: " + err.Error()), nil } return &logical.Response{ Data: map[string]interface{}{ defaultRef: parsedKey, }, }, nil } const pathConfigKeysHelpSyn = `Read and set the default key used for signing` const pathConfigKeysHelpDesc = ` This path allows configuration of key parameters. The "default" parameter controls which key is the default used by signing paths. `