pkg/berglas/create.go (151 lines of code) (raw):

// Copyright 2019 The Berglas Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package berglas import ( "context" "fmt" "path" "sort" secretspb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" "github.com/GoogleCloudPlatform/berglas/v2/pkg/berglas/logging" grpccodes "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" ) type createRequest interface { isCreateRequest() } // StorageCreateRequest is used as input to create a secret using Cloud Storage // encrypted with Cloud KMS. type StorageCreateRequest struct { // Bucket is the name of the bucket where the secret lives. Bucket string // Object is the name of the object in Cloud Storage. Object string // Key is the fully qualified KMS key id. Key string // Plaintext is the plaintext secret to encrypt and store. Plaintext []byte } func (r *StorageCreateRequest) isCreateRequest() {} // CreateRequest is an alias for StorageCreateRequest for // backwards-compatibility. New clients should use StorageCreateRequest. type CreateRequest = StorageCreateRequest // SecretManagerCreateRequest is used as input to create a secret using Secret // Manager. type SecretManagerCreateRequest struct { // Project is the ID or number of the project from which to create the secret. Project string // Name is the name of the secret to create. Name string // Plaintext is the plaintext to store. Plaintext []byte // Locations is an array indicating the canonical IDs (e.g. "us-east1") of // the locations to the replicate data at. This defaults to the automatic // replication policy when not specified. An empty array is not allowed. Locations []string } func (r *SecretManagerCreateRequest) isCreateRequest() {} // Create is a top-level package function for creating a secret. For large // volumes of secrets, please create a client instead. func Create(ctx context.Context, i createRequest) (*Secret, error) { client, err := New(ctx) if err != nil { return nil, err } return client.Create(ctx, i) } // Create creates a secret. When given a SecretManagerCreateRequest, this // creates a secret using Secret Manager. When given a StorageCreateRequest, // this creates a secret stored in Cloud Storage encrypted with Cloud KMS. // // If the secret already exists, an error is returned. Use Update to update an // existing secret. func (c *Client) Create(ctx context.Context, i createRequest) (*Secret, error) { if i == nil { return nil, fmt.Errorf("missing request") } switch t := i.(type) { case *SecretManagerCreateRequest: return c.secretManagerCreate(ctx, t) case *StorageCreateRequest: return c.storageCreate(ctx, t) default: return nil, fmt.Errorf("unknown create type %T", t) } } func (c *Client) secretManagerCreate(ctx context.Context, i *SecretManagerCreateRequest) (*Secret, error) { project := i.Project if project == "" { return nil, fmt.Errorf("missing project") } name := i.Name if name == "" { return nil, fmt.Errorf("missing secret name") } plaintext := i.Plaintext if plaintext == nil { return nil, fmt.Errorf("missing plaintext") } var replication *secretspb.Replication if len(i.Locations) == 0 { replication = &secretspb.Replication{ Replication: &secretspb.Replication_Automatic_{ Automatic: &secretspb.Replication_Automatic{}, }, } } else { sort.Strings(i.Locations) replicas := make([]*secretspb.Replication_UserManaged_Replica, len(i.Locations)) for n, loc := range i.Locations { replicas[n] = &secretspb.Replication_UserManaged_Replica{Location: loc} } replication = &secretspb.Replication{ Replication: &secretspb.Replication_UserManaged_{ UserManaged: &secretspb.Replication_UserManaged{ Replicas: replicas, }, }, } } logger := logging.FromContext(ctx).With( "project", project, "name", name, ) logger.DebugContext(ctx, "create.start") defer logger.DebugContext(ctx, "create.finish") logger.DebugContext(ctx, "creating secret") secretResp, err := c.secretManagerClient.CreateSecret(ctx, &secretspb.CreateSecretRequest{ Parent: fmt.Sprintf("projects/%s", project), SecretId: name, Secret: &secretspb.Secret{Replication: replication}, }) if err != nil { terr, ok := grpcstatus.FromError(err) if ok && terr.Code() == grpccodes.AlreadyExists { return nil, errSecretAlreadyExists } return nil, fmt.Errorf("failed to create secret: %w", err) } logger.DebugContext(ctx, "creating secret version") versionResp, err := c.secretManagerClient.AddSecretVersion(ctx, &secretspb.AddSecretVersionRequest{ Parent: secretResp.Name, Payload: &secretspb.SecretPayload{ Data: plaintext, }, }) if err != nil { return nil, fmt.Errorf("failed to create secret version: %w", err) } return &Secret{ Parent: project, Name: name, Version: path.Base(versionResp.Name), Plaintext: plaintext, UpdatedAt: timestampToTime(versionResp.CreateTime), Locations: i.Locations, }, nil } func (c *Client) storageCreate(ctx context.Context, i *StorageCreateRequest) (*Secret, error) { bucket := i.Bucket if bucket == "" { return nil, fmt.Errorf("missing bucket name") } object := i.Object if object == "" { return nil, fmt.Errorf("missing object name") } key := i.Key if key == "" { return nil, fmt.Errorf("missing key name") } plaintext := i.Plaintext if plaintext == nil { return nil, fmt.Errorf("missing plaintext") } logger := logging.FromContext(ctx).With( "bucket", bucket, "object", object, "key", key, ) logger.DebugContext(ctx, "create.start") defer logger.DebugContext(ctx, "create.finish") secret, err := c.encryptAndWrite(ctx, bucket, object, key, plaintext, 0, 0) if err != nil { return nil, fmt.Errorf("failed to create secret: %w", err) } return secret, nil }