Skip to content

Commit 34053f3

Browse files
committed
add extra file
1 parent 3b916b1 commit 34053f3

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

pkg/secrets/secrets.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Package secrets provides integration with Google Secret Manager for fetching configuration.
2+
package secrets
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"log"
8+
"os"
9+
"time"
10+
11+
secretmanager "cloud.google.com/go/secretmanager/apiv1"
12+
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
13+
"google.golang.org/api/option"
14+
)
15+
16+
const (
17+
// secretManagerTimeout prevents indefinite hangs when accessing secrets.
18+
secretManagerTimeout = 10 * time.Second
19+
)
20+
21+
// Manager handles fetching secrets from Google Secret Manager.
22+
type Manager struct {
23+
client *secretmanager.Client
24+
projectID string
25+
}
26+
27+
// New creates a new secrets manager with optional credentials.
28+
// If credentialsPath is empty, it uses Application Default Credentials.
29+
func New(ctx context.Context, projectID, credentialsPath string) (*Manager, error) {
30+
var opts []option.ClientOption
31+
if credentialsPath != "" {
32+
opts = append(opts, option.WithCredentialsFile(credentialsPath))
33+
}
34+
35+
client, err := secretmanager.NewClient(ctx, opts...)
36+
if err != nil {
37+
return nil, fmt.Errorf("failed to create secret manager client: %w", err)
38+
}
39+
40+
return &Manager{
41+
client: client,
42+
projectID: projectID,
43+
}, nil
44+
}
45+
46+
// GetWithEnvOverride fetches a secret value from Google Secret Manager,
47+
// but returns the environment variable value if it exists (env vars take precedence).
48+
// The secretName should be the same as the environment variable name (e.g., "GITHUB_WEBHOOK_SECRET").
49+
func (m *Manager) GetWithEnvOverride(ctx context.Context, envVar, secretName string) (string, error) {
50+
// Check environment variable first (takes precedence)
51+
if value := os.Getenv(envVar); value != "" {
52+
log.Printf("using environment variable instead of secret: env_var=%s source=environment", envVar)
53+
return value, nil
54+
}
55+
56+
// Build the resource name for Secret Manager
57+
resourceName := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", m.projectID, secretName)
58+
59+
log.Printf("attempting to access secret from Secret Manager: env_var=%s secret_name=%s project_id=%s", envVar, secretName, m.projectID)
60+
61+
// Create a context with timeout to prevent indefinite hangs
62+
timeoutCtx, cancel := context.WithTimeout(ctx, secretManagerTimeout)
63+
defer cancel()
64+
65+
// Access the secret version
66+
req := &secretmanagerpb.AccessSecretVersionRequest{
67+
Name: resourceName,
68+
}
69+
70+
result, err := m.client.AccessSecretVersion(timeoutCtx, req)
71+
if err != nil {
72+
log.Printf("failed to access secret from Secret Manager: env_var=%s secret_name=%s error=%v", envVar, secretName, err)
73+
return "", fmt.Errorf("failed to access secret %s: %w", resourceName, err)
74+
}
75+
76+
secretValue := string(result.GetPayload().GetData())
77+
log.Printf("successfully fetched secret from Google Secret Manager: env_var=%s secret_name=%s has_value=%v", envVar, secretName, secretValue != "")
78+
return secretValue, nil
79+
}
80+
81+
// Close closes the Secret Manager client connection.
82+
func (m *Manager) Close() error {
83+
if m.client != nil {
84+
return m.client.Close()
85+
}
86+
return nil
87+
}

0 commit comments

Comments
 (0)