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 }