-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
227 lines (188 loc) · 5.45 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package main
import (
"bytes"
"flag"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"io"
"log"
"os"
"strings"
)
type AwsClient struct {
Acct AwsAccount
SecretsManager *secretsmanager.SecretsManager
s3Client *s3.S3
s3Uploader *s3manager.Uploader
Bucket string
}
type AwsAccount struct {
Region string
AccessKeyID string
SecretAccessKey string
Session *session.Session
Role bool
}
type Secret struct {
Key string
Content *secretsmanager.GetSecretValueOutput
}
var (
client = AwsClient{}
)
func init() {
flagset := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
flagset.StringVar(&client.Acct.Region, "region", os.Getenv("AWS_REGION"), "AWS region")
flagset.StringVar(&client.Bucket, "bucket", os.Getenv("AWS_S3_BUCKET"), "AWS S3 Bucket")
flagset.BoolVar(&client.Acct.Role, "role", false, "Use AWS EC2 Instance Profile")
flagset.Parse(os.Args[1:])
}
func main() {
os.Exit(Main())
}
func Main() int {
// Create session
client.awsConfig()
client.SecretsManager = secretsmanager.New(client.Acct.Session)
// Fetch list of secrets
smList, err := client.getSecretsList()
if err != nil {
log.Fatalln(err)
}
client.s3Client = s3.New(client.Acct.Session)
for _, s := range smList {
var secret Secret
input := &secretsmanager.GetSecretValueInput{
SecretId: aws.String(s),
VersionStage: aws.String("AWSCURRENT"),
}
// Get SecretString for each value in list
secret.Content, err = client.SecretsManager.GetSecretValue(input)
// Log error, and continue to next secret
// This should handle secrets that have no value
// and other failures that are not fatal
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case secretsmanager.ErrCodeResourceNotFoundException:
log.Println("Error fetching", s, aerr.Error())
default:
log.Println(aerr.Error())
}
}
continue
}
// Build key string ie my-secret/aaa-bbb-ccc-ddd
secret.Key = strings.Join([]string{*secret.Content.Name, *secret.Content.VersionId}, "/")
// Check s3 bucket and prefix exist
objList, err := client.getObjectsList(s)
if err != nil {
log.Fatalln(err)
}
// Check if version-id object exists in bucket
found := false
for _, o := range objList {
if o == secret.Key {
log.Println("Backup is current for ", s)
found = true
break
}
}
if ! found {
log.Println("Creating key: ", secret.Key)
if err = client.uploadSecret(secret); err != nil {
log.Fatalln(err)
}
}
}
log.Println("Backup job completed.")
return 0
}
func (c *AwsClient) awsConfig() {
// Create valid AWS session
var creds *credentials.Credentials
// Check if ec2 role is true
// else check ENV
if c.Acct.Role {
sess := session.Must(session.NewSession())
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(sess),
// Do not use early expiry of credentials. If a non zero value is
// specified the credentials will be expired early
ExpiryWindow: 0,
}
creds = credentials.NewCredentials(p)
} else {
creds = credentials.NewEnvCredentials()
}
// session.Must handles setup for creating a valid session
c.Acct.Session = session.Must(session.NewSession(&aws.Config{
Credentials: creds,
S3ForcePathStyle: aws.Bool(true),
}))
}
func (c *AwsClient) getSecretsList() ([]string, error) {
var smList []string
lsi := &secretsmanager.ListSecretsInput{}
err := client.SecretsManager.ListSecretsPages(lsi,
func(page *secretsmanager.ListSecretsOutput, lastPage bool) bool {
var nextPage bool = false
for _, p := range page.SecretList {
smList = append(smList, *p.Name)
}
if page.NextToken != nil {
lsi.SetNextToken(*page.NextToken)
nextPage = true
}
return nextPage
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
// Get error details
return smList, fmt.Errorf("Error Code: %v Message: %v\n", awsErr.Code(), awsErr.Message())
}
}
if len(smList) == 0 {
return smList, fmt.Errorf(("No secrets found. Verify account and permissions."))
}
return smList, nil
}
func (c *AwsClient) getObjectsList(prefix string) ([]string, error) {
var files []string
req := &s3.ListObjectsV2Input{Bucket: aws.String(c.Bucket), Prefix: aws.String(prefix)}
err := c.s3Client.ListObjectsV2Pages(req, func(resp *s3.ListObjectsV2Output, lastPage bool) bool {
for _, content := range resp.Contents {
files = append(files, *content.Key)
}
return true
})
if err != nil {
return files, fmt.Errorf("failed to list objects for bucket %v: %v", c.Bucket, err)
}
return files, nil
}
func (c *AwsClient) uploadSecret(s Secret) error {
// Chunk uploads
c.s3Uploader = s3manager.NewUploader(c.Acct.Session, func(u *s3manager.Uploader) {
u.PartSize = 64 * 1024 * 1024 // 64MB per part
})
// Convert string using bytes Reader to use with io.ReadSeeker Reader interface
f := bytes.NewReader([]byte(*s.Content.SecretString))
_, err := c.s3Uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String(c.Bucket),
Key: aws.String(s.Key),
Body: io.ReadSeeker(f),
})
if err != nil {
return fmt.Errorf("unable to upload %q to %q, %v", s.Key, c.Bucket, err)
}
return nil
}