blob: 28d47a3d201601fa9f70d7c059a2efabe5c714d8 [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package secret provides a client interface for interacting
// with the GCP Secret Management service.
package secret
import (
"context"
"fmt"
"io"
"log"
"path"
"cloud.google.com/go/compute/metadata"
secretmanager "cloud.google.com/go/secretmanager/apiv1"
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
gax "github.com/googleapis/gax-go/v2"
)
const (
// NameBuilderMasterKey is the secret name for the builder master key.
NameBuilderMasterKey = "builder-master-key"
// NameFarmerRunBench is the secret name for farmer run bench.
NameFarmerRunBench = "farmer-run-bench"
// NameGerritbotGitCookies is the secret name for Gerritbot Git cookies.
NameGerritbotGitCookies = "gerritbot-gitcookies"
// NameGitHubSSH is the secret name for GitHub SSH key.
NameGitHubSSH = "github-ssh"
// NameGitHubSSHKey is the secret name for the GitHub SSH private key.
NameGitHubSSHKey = "github-ssh-private-key"
// NameGobotPassword is the secret name for the gobot@golang.org Gerrit account password.
NameGobotPassword = "gobot-password"
// NameGomoteSSHCAPrivateKey is the secret name for the gomote SSH certificate authority private key.
NameGomoteSSHCAPrivateKey = "gomote-ssh-ca-private-key"
// NameGomoteSSHCAPublicKey is the secret name for the gomote SSH certificate authority public key.
NameGomoteSSHCAPublicKey = "gomote-ssh-ca-public-key"
// NameGomoteSSHPrivateKey is the secret name for the gomote SSH private key.
NameGomoteSSHPrivateKey = "gomote-ssh-private-key"
// NameGomoteSSHPublicKey is the secret name for the gomote SSH public key.
NameGomoteSSHPublicKey = "gomote-ssh-public-key"
// NameMaintnerGitHubToken is the secret name for the Maintner GitHub token.
NameMaintnerGitHubToken = "maintner-github-token"
// NameWatchflakesGitHubToken is the secret name for the watchflakes GitHub token.
NameWatchflakesGitHubToken = "watchflakes-github-token"
// NameGitHubWebhookSecret is the secret name for a golang/go GitHub webhook secret.
NameGitHubWebhookSecret = "github-webhook-secret"
// NamePubSubHelperWebhook is the secret name for the pubsub helper webhook secret.
NamePubSubHelperWebhook = "pubsubhelper-webhook-secret"
// NameAWSAccessKey is the secret name for the AWS access key.
NameAWSAccessKey = "aws-access-key"
// NameAWSKeyID is the secret name for the AWS key id.
NameAWSKeyID = "aws-key-id"
// NameSendGridAPIKey is the secret name for a Go project SendGrid API key.
// This API key only allows sending email.
NameSendGridAPIKey = "sendgrid-sendonly-api-key"
// NameTwitterAPISecret is the secret name for Twitter API credentials for
// posting tweets from the Go project's Twitter account (twitter.com/golang).
//
// The secret value encodes relevant keys and their secrets as
// a JSON object that can be unmarshaled into TwitterCredentials:
//
// {
// "ConsumerKey": "...",
// "ConsumerSecret": "...",
// "AccessTokenKey": "...",
// "AccessTokenSecret": "..."
// }
NameTwitterAPISecret = "twitter-api-secret"
// NameStagingTwitterAPISecret is the secret name for Twitter API credentials
// for posting tweets using a staging test Twitter account.
//
// This secret is available in the Secret Manager of the x/build staging GCP project.
//
// The secret value encodes relevant keys and their secrets as
// a JSON object that can be unmarshaled into TwitterCredentials.
NameStagingTwitterAPISecret = "staging-" + NameTwitterAPISecret
// NameMastodonAPISecret is the secret name for Mastodon API credentials
// for posting to Hachyderm.io/@golang. The secret value is a JSON
// encoding of the MastodonCredentials.
NameMastodonAPISecret = "mastodon-api-secret"
// NameMacServiceAPIKey is the secret name for the MacService API key.
NameMacServiceAPIKey = "macservice-api-key"
)
// TwitterCredentials holds Twitter API credentials.
type TwitterCredentials struct {
ConsumerKey string
ConsumerSecret string
AccessTokenKey string
AccessTokenSecret string
}
type MastodonCredentials struct {
// Log in to <Instance> as your bot account,
// navigate to Profile -> Development,
// Click on <Application> in the Application column,
// and it will reveal Client Key, Client Secret, and Access Token
Instance string // Instance (e.g. "botsin.space")
Application string // Application name (e.g. ""Go benchmarking bot"")
ClientKey string // Client Key
ClientSecret string // Client secret
AccessToken string // Access token
TestRecipient string // For testing only, ignored by non-test API
}
func (t TwitterCredentials) String() string {
return fmt.Sprintf("{%s (redacted) %s (redacted)}", t.ConsumerKey, t.AccessTokenKey)
}
func (t TwitterCredentials) GoString() string {
return fmt.Sprintf("secret.TwitterCredentials{ConsumerKey:%q ConsumerSecret:(redacted) AccessTokenKey:%q AccessTokenSecret:(redacted)}", t.ConsumerKey, t.AccessTokenKey)
}
func (t MastodonCredentials) String() string {
return fmt.Sprintf("{%s %s (redacted) (redacted) (redacted)}", t.Instance, t.Application)
}
func (t MastodonCredentials) GoString() string {
return fmt.Sprintf("secret.MastodonCredentials{Instance:%q Application:%q ClientKey:(redacted) ClientSecret:(redacted) AccessToken:(redacted)}", t.Instance, t.Application)
}
type secretClient interface {
AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
io.Closer
}
// Client is used to interact with the GCP Secret Management service.
type Client struct {
client secretClient
projectID string // projectID specifies the ID of the GCP project where secrets are retrieved from.
}
// NewClient creates a Secret Manager Client
// that targets the current GCP instance's project ID.
func NewClient() (*Client, error) {
projectID, err := metadata.ProjectID()
if err != nil {
return nil, err
}
// The default client configuration includes retries on transient failures.
// It is a non-blocking blocking call which is why we do not set a timeout on
// the context.
client, err := secretmanager.NewClient(context.Background())
if err != nil {
return nil, err
}
return &Client{
client: client,
projectID: projectID,
}, nil
}
// NewClientInProject creates a Secret Manager Client
// that targets the specified GCP project ID.
func NewClientInProject(projectID string) (*Client, error) {
client, err := secretmanager.NewClient(context.Background())
if err != nil {
return nil, err
}
return &Client{
client: client,
projectID: projectID,
}, nil
}
// Retrieve the named secret from the Secret Management service.
func (smc *Client) Retrieve(ctx context.Context, name string) (string, error) {
r, err := smc.client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
Name: buildNamePath(smc.projectID, name, "latest"),
})
if err != nil {
return "", err
}
return string(r.Payload.GetData()), nil
}
// Close closes the connection to the Secret Management service.
func (smc *Client) Close() error {
return smc.client.Close()
}
// buildNamePath creates the name path required by the Secret Management service to
// query for a secret.
func buildNamePath(projectID, name, version string) string {
return path.Join("projects", projectID, "secrets", name, "versions", version)
}
// MustNewClient instantiates an instance of the Secret Manager Client. If there is an error
// this function will exit.
func MustNewClient() *Client {
c, err := NewClient()
if err != nil {
log.Fatalf("unable to create secret client %v", err)
}
return c
}