golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/secret/gcp_secret_manager.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package secret provides a client interface for interacting
     6  // with the GCP Secret Management service.
     7  package secret
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"path"
    15  
    16  	"cloud.google.com/go/compute/metadata"
    17  	secretmanager "cloud.google.com/go/secretmanager/apiv1"
    18  	"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
    19  	gax "github.com/googleapis/gax-go/v2"
    20  )
    21  
    22  const (
    23  	// NameBuilderMasterKey is the secret name for the builder master key.
    24  	NameBuilderMasterKey = "builder-master-key"
    25  
    26  	// NameFarmerRunBench is the secret name for farmer run bench.
    27  	NameFarmerRunBench = "farmer-run-bench"
    28  
    29  	// NameGerritbotGitCookies is the secret name for Gerritbot Git cookies.
    30  	NameGerritbotGitCookies = "gerritbot-gitcookies"
    31  
    32  	// NameGitHubSSH is the secret name for GitHub SSH key.
    33  	NameGitHubSSH = "github-ssh"
    34  
    35  	// NameGitHubSSHKey is the secret name for the GitHub SSH private key.
    36  	NameGitHubSSHKey = "github-ssh-private-key"
    37  
    38  	// NameGobotPassword is the secret name for the gobot@golang.org Gerrit account password.
    39  	NameGobotPassword = "gobot-password"
    40  
    41  	// NameGomoteSSHCAPrivateKey is the secret name for the gomote SSH certificate authority private key.
    42  	NameGomoteSSHCAPrivateKey = "gomote-ssh-ca-private-key"
    43  
    44  	// NameGomoteSSHCAPublicKey is the secret name for the gomote SSH certificate authority public key.
    45  	NameGomoteSSHCAPublicKey = "gomote-ssh-ca-public-key"
    46  
    47  	// NameGomoteSSHPrivateKey is the secret name for the gomote SSH private key.
    48  	NameGomoteSSHPrivateKey = "gomote-ssh-private-key"
    49  
    50  	// NameGomoteSSHPublicKey is the secret name for the gomote SSH public key.
    51  	NameGomoteSSHPublicKey = "gomote-ssh-public-key"
    52  
    53  	// NameMaintnerGitHubToken is the secret name for the Maintner GitHub token.
    54  	NameMaintnerGitHubToken = "maintner-github-token"
    55  
    56  	// NameWatchflakesGitHubToken is the secret name for the watchflakes GitHub token.
    57  	NameWatchflakesGitHubToken = "watchflakes-github-token"
    58  
    59  	// NameGitHubWebhookSecret is the secret name for a golang/go GitHub webhook secret.
    60  	NameGitHubWebhookSecret = "github-webhook-secret"
    61  
    62  	// NamePubSubHelperWebhook is the secret name for the pubsub helper webhook secret.
    63  	NamePubSubHelperWebhook = "pubsubhelper-webhook-secret"
    64  
    65  	// NameAWSAccessKey is the secret name for the AWS access key.
    66  	NameAWSAccessKey = "aws-access-key"
    67  
    68  	// NameAWSKeyID is the secret name for the AWS key id.
    69  	NameAWSKeyID = "aws-key-id"
    70  
    71  	// NameSendGridAPIKey is the secret name for a Go project SendGrid API key.
    72  	// This API key only allows sending email.
    73  	NameSendGridAPIKey = "sendgrid-sendonly-api-key"
    74  
    75  	// NameTwitterAPISecret is the secret name for Twitter API credentials for
    76  	// posting tweets from the Go project's Twitter account (twitter.com/golang).
    77  	//
    78  	// The secret value encodes relevant keys and their secrets as
    79  	// a JSON object that can be unmarshaled into TwitterCredentials:
    80  	//
    81  	// 	{
    82  	// 		"ConsumerKey":       "...",
    83  	// 		"ConsumerSecret":    "...",
    84  	// 		"AccessTokenKey":    "...",
    85  	// 		"AccessTokenSecret": "..."
    86  	// 	}
    87  	NameTwitterAPISecret = "twitter-api-secret"
    88  	// NameStagingTwitterAPISecret is the secret name for Twitter API credentials
    89  	// for posting tweets using a staging test Twitter account.
    90  	//
    91  	// This secret is available in the Secret Manager of the x/build staging GCP project.
    92  	//
    93  	// The secret value encodes relevant keys and their secrets as
    94  	// a JSON object that can be unmarshaled into TwitterCredentials.
    95  	NameStagingTwitterAPISecret = "staging-" + NameTwitterAPISecret
    96  
    97  	// NameMastodonAPISecret is the secret name for Mastodon API credentials
    98  	// for posting to Hachyderm.io/@golang.  The secret value is a JSON
    99  	// encoding of the MastodonCredentials.
   100  	NameMastodonAPISecret = "mastodon-api-secret"
   101  
   102  	// NameMacServiceAPIKey is the secret name for the MacService API key.
   103  	NameMacServiceAPIKey = "macservice-api-key"
   104  )
   105  
   106  // TwitterCredentials holds Twitter API credentials.
   107  type TwitterCredentials struct {
   108  	ConsumerKey       string
   109  	ConsumerSecret    string
   110  	AccessTokenKey    string
   111  	AccessTokenSecret string
   112  }
   113  
   114  type MastodonCredentials struct {
   115  	// Log in to <Instance> as your bot account,
   116  	// navigate to Profile -> Development,
   117  	// Click on <Application> in the Application column,
   118  	// and it will reveal Client Key, Client Secret, and Access Token
   119  	Instance      string // Instance (e.g. "botsin.space")
   120  	Application   string // Application name (e.g. ""Go benchmarking bot"")
   121  	ClientKey     string // Client Key
   122  	ClientSecret  string // Client secret
   123  	AccessToken   string // Access token
   124  	TestRecipient string // For testing only, ignored by non-test API
   125  }
   126  
   127  func (t TwitterCredentials) String() string {
   128  	return fmt.Sprintf("{%s (redacted) %s (redacted)}", t.ConsumerKey, t.AccessTokenKey)
   129  }
   130  func (t TwitterCredentials) GoString() string {
   131  	return fmt.Sprintf("secret.TwitterCredentials{ConsumerKey:%q ConsumerSecret:(redacted) AccessTokenKey:%q AccessTokenSecret:(redacted)}", t.ConsumerKey, t.AccessTokenKey)
   132  }
   133  
   134  func (t MastodonCredentials) String() string {
   135  	return fmt.Sprintf("{%s %s (redacted) (redacted) (redacted)}", t.Instance, t.Application)
   136  }
   137  func (t MastodonCredentials) GoString() string {
   138  	return fmt.Sprintf("secret.MastodonCredentials{Instance:%q Application:%q ClientKey:(redacted) ClientSecret:(redacted) AccessToken:(redacted)}", t.Instance, t.Application)
   139  }
   140  
   141  type secretClient interface {
   142  	AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
   143  	io.Closer
   144  }
   145  
   146  // Client is used to interact with the GCP Secret Management service.
   147  type Client struct {
   148  	client    secretClient
   149  	projectID string // projectID specifies the ID of the GCP project where secrets are retrieved from.
   150  }
   151  
   152  // NewClient creates a Secret Manager Client
   153  // that targets the current GCP instance's project ID.
   154  func NewClient() (*Client, error) {
   155  	projectID, err := metadata.ProjectID()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	// The default client configuration includes retries on transient failures.
   161  	// It is a non-blocking blocking call which is why we do not set a timeout on
   162  	// the context.
   163  	client, err := secretmanager.NewClient(context.Background())
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	return &Client{
   169  		client:    client,
   170  		projectID: projectID,
   171  	}, nil
   172  }
   173  
   174  // NewClientInProject creates a Secret Manager Client
   175  // that targets the specified GCP project ID.
   176  func NewClientInProject(projectID string) (*Client, error) {
   177  	client, err := secretmanager.NewClient(context.Background())
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	return &Client{
   182  		client:    client,
   183  		projectID: projectID,
   184  	}, nil
   185  }
   186  
   187  // Retrieve the named secret from the Secret Management service.
   188  func (smc *Client) Retrieve(ctx context.Context, name string) (string, error) {
   189  	r, err := smc.client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
   190  		Name: buildNamePath(smc.projectID, name, "latest"),
   191  	})
   192  	if err != nil {
   193  		return "", err
   194  	}
   195  	return string(r.Payload.GetData()), nil
   196  }
   197  
   198  // Close closes the connection to the Secret Management service.
   199  func (smc *Client) Close() error {
   200  	return smc.client.Close()
   201  }
   202  
   203  // buildNamePath creates the name path required by the Secret Management service to
   204  // query for a secret.
   205  func buildNamePath(projectID, name, version string) string {
   206  	return path.Join("projects", projectID, "secrets", name, "versions", version)
   207  }
   208  
   209  // MustNewClient instantiates an instance of the Secret Manager Client. If there is an error
   210  // this function will exit.
   211  func MustNewClient() *Client {
   212  	c, err := NewClient()
   213  	if err != nil {
   214  		log.Fatalf("unable to create secret client %v", err)
   215  	}
   216  	return c
   217  }