func()

in pkg/berglas/writer.go [30:130]


func (c *Client) encryptAndWrite(
	ctx context.Context, bucket, object, key string, plaintext []byte,
	generation, metageneration int64) (*Secret, error) {

	logger := logging.FromContext(ctx).With(
		"bucket", bucket,
		"object", object,
		"key", key,
		"generation", generation,
		"metageneration", metageneration,
	)

	logger.DebugContext(ctx, "encryptAndWrite.start")
	defer logger.DebugContext(ctx, "encryptAndWrite.finish")

	// Generate a unique DEK and encrypt the plaintext locally (useful for large
	// pieces of data).
	logger.DebugContext(ctx, "generating envelope")
	dek, ciphertext, err := envelopeEncrypt(plaintext)
	if err != nil {
		return nil, fmt.Errorf("failed to perform envelope encryption: %w", err)
	}

	// Encrypt the plaintext using a KMS key
	logger.DebugContext(ctx, "encrypting envelope")
	kmsResp, err := c.kmsClient.Encrypt(ctx, &kmspb.EncryptRequest{
		Name:                        key,
		Plaintext:                   dek,
		AdditionalAuthenticatedData: []byte(object),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to encrypt secret: %w", err)
	}
	encDEK := kmsResp.Ciphertext

	// Build the storage object contents. Contents will be of the format:
	//
	//    b64(kms_encrypted_dek):b64(dek_encrypted_plaintext)
	blob := fmt.Sprintf("%s:%s",
		base64.StdEncoding.EncodeToString(encDEK),
		base64.StdEncoding.EncodeToString(ciphertext))

	// If generation and metageneration are 0, then we should only create the
	// object if it does not exist. Otherwise, we should only perform an update if
	// the metagenerations match.
	var conds storage.Conditions
	if generation == 0 || metageneration == 0 {
		conds = storage.Conditions{
			DoesNotExist: true,
		}
	} else {
		conds = storage.Conditions{
			GenerationMatch:     generation,
			MetagenerationMatch: metageneration,
		}
	}

	// Create the writer
	iow := c.storageClient.
		Bucket(bucket).
		Object(object).
		If(conds).
		NewWriter(ctx)

	iow.ObjectAttrs.CacheControl = CacheControl
	iow.ChunkSize = ChunkSize

	if iow.Metadata == nil {
		iow.Metadata = make(map[string]string)
	}
	iow.Metadata[MetadataIDKey] = "1"
	iow.Metadata[MetadataKMSKey] = kmsKeyTrimVersion(key)

	// Write
	logger.DebugContext(ctx, "writing object to storage", "metadata", iow.Metadata)
	if _, err := iow.Write([]byte(blob)); err != nil {
		return nil, fmt.Errorf("failed to save encrypted ciphertext to storage: %w", err)
	}

	// Close and flush
	logger.DebugContext(ctx, "finalizing writer")
	if err := iow.Close(); err != nil {
		logger.ErrorContext(ctx, "failed to initialize writer", "error", err)

		if terr, ok := err.(*googleapi.Error); ok {
			switch terr.Code {
			case http.StatusNotFound:
				return nil, fmt.Errorf("bucket does not exist")
			case http.StatusPreconditionFailed:
				if conds.DoesNotExist {
					return nil, errSecretAlreadyExists
				}
				return nil, errSecretModified
			}
		}

		return nil, fmt.Errorf("failed to write to bucket: %w", err)
	}

	return secretFromAttrs(bucket, iow.Attrs(), plaintext), nil
}