| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package ssh |
| |
| import ( |
| "context" |
| "fmt" |
| |
| "github.com/hashicorp/vault/sdk/framework" |
| "github.com/hashicorp/vault/sdk/logical" |
| ) |
| |
| func pathSign(b *backend) *framework.Path { |
| return &framework.Path{ |
| Pattern: "sign/" + framework.GenericNameWithAtRegex("role"), |
| |
| DisplayAttrs: &framework.DisplayAttributes{ |
| OperationPrefix: operationPrefixSSH, |
| OperationVerb: "sign", |
| OperationSuffix: "certificate", |
| }, |
| |
| Callbacks: map[logical.Operation]framework.OperationFunc{ |
| logical.UpdateOperation: b.pathSign, |
| }, |
| |
| Fields: map[string]*framework.FieldSchema{ |
| "role": { |
| Type: framework.TypeString, |
| Description: `The desired role with configuration for this request.`, |
| }, |
| "ttl": { |
| Type: framework.TypeDurationSecond, |
| Description: `The requested Time To Live for the SSH certificate; |
| sets the expiration date. If not specified |
| the role default, backend default, or system |
| default TTL is used, in that order. Cannot |
| be later than the role max TTL.`, |
| }, |
| "public_key": { |
| Type: framework.TypeString, |
| Description: `SSH public key that should be signed.`, |
| }, |
| "valid_principals": { |
| Type: framework.TypeString, |
| Description: `Valid principals, either usernames or hostnames, that the certificate should be signed for.`, |
| }, |
| "cert_type": { |
| Type: framework.TypeString, |
| Description: `Type of certificate to be created; either "user" or "host".`, |
| Default: "user", |
| }, |
| "key_id": { |
| Type: framework.TypeString, |
| Description: `Key id that the created certificate should have. If not specified, the display name of the token will be used.`, |
| }, |
| "critical_options": { |
| Type: framework.TypeMap, |
| Description: `Critical options that the certificate should be signed for.`, |
| }, |
| "extensions": { |
| Type: framework.TypeMap, |
| Description: `Extensions that the certificate should be signed for.`, |
| }, |
| }, |
| |
| HelpSynopsis: `Request signing an SSH key using a certain role with the provided details.`, |
| HelpDescription: `This path allows SSH keys to be signed according to the policy of the given role.`, |
| } |
| } |
| |
| func (b *backend) pathSign(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { |
| roleName := data.Get("role").(string) |
| |
| // Get the role |
| role, err := b.getRole(ctx, req.Storage, roleName) |
| if err != nil { |
| return nil, err |
| } |
| if role == nil { |
| return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", roleName)), nil |
| } |
| |
| return b.pathSignCertificate(ctx, req, data, role) |
| } |
| |
| func (b *backend) pathSignCertificate(ctx context.Context, req *logical.Request, data *framework.FieldData, role *sshRole) (*logical.Response, error) { |
| publicKey := data.Get("public_key").(string) |
| if publicKey == "" { |
| return logical.ErrorResponse("missing public_key"), nil |
| } |
| |
| userPublicKey, err := parsePublicSSHKey(publicKey) |
| if err != nil { |
| return logical.ErrorResponse(fmt.Sprintf("failed to parse public_key as SSH key: %s", err)), nil |
| } |
| |
| err = b.validateSignedKeyRequirements(userPublicKey, role) |
| if err != nil { |
| return logical.ErrorResponse(fmt.Sprintf("public_key failed to meet the key requirements: %s", err)), nil |
| } |
| |
| return b.pathSignIssueCertificateHelper(ctx, req, data, role, userPublicKey) |
| } |