/
s3Repository.go
167 lines (146 loc) · 5.64 KB
/
s3Repository.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
package restic
import (
"context"
"database/sql"
"fmt"
"os"
"os/exec"
"time"
"github.com/liuminhaw/wrestic-brw/utils/encryptor"
)
const (
awsAccessKeyIdEnv string = "AWS_ACCESS_KEY_ID"
awsSecretAccessKeyEnv string = "AWS_SECRET_ACCESS_KEY"
)
// Encrypted access key ID for S3 repository
// Encrypted secret access key for S3 repository
// Encrypted password for S3 repository
type S3RepositoryEnc struct {
AccessKeyIdEnc string
SecretAccessKeyEnc string
PasswordEnc string
}
// Id represents the unique identifier of the S3 repository.
// Name represents the name of the S3 repository.
// Password represents the password for accessing the S3 repository.
// Destination represents the target location where the S3 repository is stored.
// AccessKeyId represents the access key ID for authenticating with the S3 repository.
// SecretAccessKey represents the secret access key for authenticating with the S3 repository.
// Region represents the AWS region where the S3 repository is located.
// ConfigId represents the configuration identifier associated with the S3 repository.
// Encryption represents the encryption settings for the S3 repository.
type S3Repository struct {
Id int
Name string
Password string
Destination string
AccessKeyId string
SecretAccessKey string
Region string
ConfigId int
Encryption *S3RepositoryEnc
}
// connect establishes a connection to the S3 repository.
// It sets the password environment variable, initializes the credentials,
// and executes a restic command to check the connection.
// If the connection times out, it returns ErrConnectionTimeout.
// If there is an error during the connection, it returns an error with the message "restic connect: <original error>".
// Otherwise, it returns nil.
func (r *S3Repository) connect() error {
os.Setenv(passwordEnv, r.Password)
r.initCredential()
commandArg := []string{"cat", "config", "-r", fmt.Sprintf("s3:s3.amazonaws.com/%s", r.Destination)}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, resticCmd, commandArg...)
_, err := cmd.Output()
if ctx.Err() == context.DeadlineExceeded {
return ErrConnectionTimeout
}
if err != nil {
return fmt.Errorf("restic connect: %w", err)
}
return nil
}
// newRepo creates a new S3 repository in the database.
// It takes a DB connection as input and returns an error if any.
// The function inserts the repository details into the "repositories" table
// and the S3 repository configuration into the "s3_repository_configs" table.
// It uses a transaction to ensure atomicity and rolls back the transaction if any error occurs.
func (r *S3Repository) newRepo(DB *sql.DB, userId int) error {
tx, err := DB.Begin()
if err != nil {
return fmt.Errorf("create s3 repository: new transaction: %w", err)
}
row := tx.QueryRow(`
INSERT INTO "repositories" ("name", "destination", "password_enc", "type_id", "owner_id")
VALUES ($1, $2, $3, (
SELECT "id"
FROM "repository_types"
WHERE "name" = $4
), $5)
RETURNING ID;
`, r.Name, r.Destination, r.Encryption.PasswordEnc, s3Type, userId)
err = row.Scan(&r.Id)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("create s3 repository: transaction rollback: %w", rbErr)
}
return fmt.Errorf("create s3 repository: %w", err)
}
row = tx.QueryRow(`
INSERT INTO "s3_repository_configs" ("access_key_id_enc", "secret_access_key_enc", "region", "repository_id")
VALUES ($1, $2, $3, $4)
RETURNING ID;
`, r.Encryption.AccessKeyIdEnc, r.Encryption.SecretAccessKeyEnc, r.Region, r.Id)
err = row.Scan(&r.ConfigId)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("create s3 repository: transaction rollback: %w", rbErr)
}
return fmt.Errorf("create s3 repository config: %w", err)
}
_, err = tx.Exec(`
INSERT INTO "repository_settings" ("snapshot_check_interval", "repository_id")
VALUES ($1, $2);
`, defaultSnapshotCheckInterval, r.Id)
if err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("create s3 repository: transaction rollback: %w", rbErr)
}
return fmt.Errorf("create s3 repository settings: %w", err)
}
err = tx.Commit()
if err != nil {
return fmt.Errorf("create s3 repository: transaction commit: %w", err)
}
return nil
}
// GenEnc generates encrypted versions of the S3 repository's access key ID, secret access key, and password.
// It takes an encryption key as input and encrypts the values using the encryptor package.
// The encrypted values are then stored in the Encryption struct of the S3Repository.
// If any encryption operation fails, an error is returned.
func (r *S3Repository) GenEnc(encKey [32]byte) error {
enc, err := encryptor.Encrypt([]byte(r.AccessKeyId), encKey)
if err != nil {
return fmt.Errorf("gen s3 repository encryption: access key id: %w", err)
}
r.Encryption.AccessKeyIdEnc = enc
enc, err = encryptor.Encrypt([]byte(r.SecretAccessKey), encKey)
if err != nil {
return fmt.Errorf("gen s3 repository encryption: secret access key: %w", err)
}
r.Encryption.SecretAccessKeyEnc = enc
enc, err = encryptor.Encrypt([]byte(r.Password), encKey)
if err != nil {
return fmt.Errorf("gen s3 repository encryption: password: %w", err)
}
r.Encryption.PasswordEnc = enc
return nil
}
// initCredential initializes the AWS credentials for the S3Repository.
// It sets the AWS access key ID and secret access key as environment variables.
func (r *S3Repository) initCredential() {
os.Setenv(awsAccessKeyIdEnv, r.AccessKeyId)
os.Setenv(awsSecretAccessKeyEnv, r.SecretAccessKey)
}