1
1
package models
2
2
3
3
import (
4
+ "bytes"
5
+ "context"
6
+ "encoding/base64"
7
+ "fmt"
8
+ "math/rand"
4
9
"net/url"
10
+ "os"
11
+ "os/exec"
12
+ "path/filepath"
5
13
"regexp"
14
+ "strings"
6
15
"time"
7
16
8
17
"github.com/flanksource/duty/types"
9
18
"github.com/google/uuid"
10
19
)
11
20
21
+ // List of all connection types
22
+ const (
23
+ ConnectionTypeAWS = "aws"
24
+ ConnectionTypeAzure = "azure"
25
+ ConnectionTypeAzureDevops = "azure_devops"
26
+ ConnectionTypeDiscord = "discord"
27
+ ConnectionTypeDynatrace = "dynatrace"
28
+ ConnectionTypeElasticSearch = "elasticsearch"
29
+ ConnectionTypeEmail = "email"
30
+ ConnectionTypeGCP = "google_cloud"
31
+ ConnectionTypeGenericWebhook = "generic_webhook"
32
+ ConnectionTypeGit = "git"
33
+ ConnectionTypeGithub = "github"
34
+ ConnectionTypeGoogleChat = "google_chat"
35
+ ConnectionTypeHTTP = "http"
36
+ ConnectionTypeIFTTT = "ifttt"
37
+ ConnectionTypeJMeter = "jmeter"
38
+ ConnectionTypeKubernetes = "kubernetes"
39
+ ConnectionTypeLDAP = "ldap"
40
+ ConnectionTypeMatrix = "matrix"
41
+ ConnectionTypeMattermost = "mattermost"
42
+ ConnectionTypeMongo = "mongo"
43
+ ConnectionTypeMySQL = "mysql"
44
+ ConnectionTypeNtfy = "ntfy"
45
+ ConnectionTypeOpsGenie = "opsgenie"
46
+ ConnectionTypePostgres = "postgres"
47
+ ConnectionTypePrometheus = "prometheus"
48
+ ConnectionTypePushbullet = "pushbullet"
49
+ ConnectionTypePushover = "pushover"
50
+ ConnectionTypeRedis = "redis"
51
+ ConnectionTypeRestic = "restic"
52
+ ConnectionTypeRocketchat = "rocketchat"
53
+ ConnectionTypeSFTP = "sftp"
54
+ ConnectionTypeSlack = "slack"
55
+ ConnectionTypeSlackWebhook = "slackwebhook"
56
+ ConnectionTypeSMB = "smb"
57
+ ConnectionTypeSQLServer = "sql_server"
58
+ ConnectionTypeTeams = "teams"
59
+ ConnectionTypeTelegram = "telegram"
60
+ ConnectionTypeWebhook = "webhook"
61
+ ConnectionTypeWindows = "windows"
62
+ ConnectionTypeZulipChat = "zulip_chat"
63
+ )
64
+
12
65
type Connection struct {
13
66
ID uuid.UUID `gorm:"primaryKey;unique_index;not null;column:id" json:"id" faker:"uuid_hyphenated" `
14
67
Name string `gorm:"column:name" json:"name" faker:"name" `
@@ -25,9 +78,10 @@ type Connection struct {
25
78
}
26
79
27
80
func (c Connection ) String () string {
28
- if c .Type == "aws" {
81
+ if strings . ToLower ( c .Type ) == ConnectionTypeAWS {
29
82
return "AWS::" + c .Username
30
83
}
84
+
31
85
var connection string
32
86
// Obfuscate passwords of the form ' password=xxxxx ' from connectionString since
33
87
// connectionStrings are used as metric labels and we don't want to leak passwords
@@ -48,3 +102,166 @@ func (c Connection) String() string {
48
102
func (c Connection ) AsMap (removeFields ... string ) map [string ]any {
49
103
return asMap (c , removeFields ... )
50
104
}
105
+
106
+ // AsGoGetterURL returns the connection as a url that's supported by https://github.com/hashicorp/go-getter
107
+ // Connection details are added to the url as query params
108
+ func (c Connection ) AsGoGetterURL () (string , error ) {
109
+ parsedURL , err := url .Parse (c .URL )
110
+ if err != nil {
111
+ return "" , err
112
+ }
113
+
114
+ var output string
115
+ switch strings .ReplaceAll (strings .ToLower (c .Type ), " " , "_" ) {
116
+ case ConnectionTypeHTTP :
117
+ if c .Username != "" || c .Password != "" {
118
+ parsedURL .User = url .UserPassword (c .Username , c .Password )
119
+ }
120
+
121
+ output = parsedURL .String ()
122
+
123
+ case ConnectionTypeGit :
124
+ q := parsedURL .Query ()
125
+
126
+ if c .Certificate != "" {
127
+ q .Set ("sshkey" , base64 .URLEncoding .EncodeToString ([]byte (c .Certificate )))
128
+ }
129
+
130
+ if v , ok := c .Properties ["ref" ]; ok {
131
+ q .Set ("ref" , v )
132
+ }
133
+
134
+ if v , ok := c .Properties ["depth" ]; ok {
135
+ q .Set ("depth" , v )
136
+ }
137
+
138
+ parsedURL .RawQuery = q .Encode ()
139
+ output = parsedURL .String ()
140
+
141
+ case ConnectionTypeAWS :
142
+ q := parsedURL .Query ()
143
+ q .Set ("aws_access_key_id" , c .Username )
144
+ q .Set ("aws_access_key_secret" , c .Password )
145
+
146
+ if v , ok := c .Properties ["profile" ]; ok {
147
+ q .Set ("aws_profile" , v )
148
+ }
149
+
150
+ if v , ok := c .Properties ["region" ]; ok {
151
+ q .Set ("region" , v )
152
+ }
153
+
154
+ // For S3
155
+ if v , ok := c .Properties ["version" ]; ok {
156
+ q .Set ("version" , v )
157
+ }
158
+
159
+ parsedURL .RawQuery = q .Encode ()
160
+ output = parsedURL .String ()
161
+ }
162
+
163
+ return output , nil
164
+ }
165
+
166
+ // AsEnv generates environment variables and a configuration file content based on the connection type.
167
+ func (c Connection ) AsEnv (ctx context.Context ) EnvPrep {
168
+ var envPrep = EnvPrep {
169
+ Files : make (map [string ]bytes.Buffer ),
170
+ }
171
+
172
+ switch strings .ReplaceAll (strings .ToLower (c .Type ), " " , "_" ) {
173
+ case ConnectionTypeAWS :
174
+ envPrep .Env = append (envPrep .Env , fmt .Sprintf ("AWS_ACCESS_KEY_ID=%s" , c .Username ))
175
+ envPrep .Env = append (envPrep .Env , fmt .Sprintf ("AWS_SECRET_ACCESS_KEY=%s" , c .Password ))
176
+
177
+ // credentialFilePath :="$HOME/.aws/credentials"
178
+ credentialFilePath := filepath .Join (".creds" , "aws" , fmt .Sprintf ("cred-%d" , rand .Intn (100000000 )))
179
+
180
+ var credentialFile bytes.Buffer
181
+ credentialFile .WriteString ("[default]\n " )
182
+ credentialFile .WriteString (fmt .Sprintf ("aws_access_key_id = %s\n " , c .Username ))
183
+ credentialFile .WriteString (fmt .Sprintf ("aws_secret_access_key = %s\n " , c .Password ))
184
+
185
+ if v , ok := c .Properties ["profile" ]; ok {
186
+ envPrep .Env = append (envPrep .Env , fmt .Sprintf ("AWS_DEFAULT_PROFILE=%s" , v ))
187
+ }
188
+
189
+ if v , ok := c .Properties ["region" ]; ok {
190
+ envPrep .Env = append (envPrep .Env , fmt .Sprintf ("AWS_DEFAULT_REGION=%s" , v ))
191
+
192
+ credentialFile .WriteString (fmt .Sprintf ("region = %s\n " , v ))
193
+
194
+ envPrep .CmdEnvs = append (envPrep .CmdEnvs , fmt .Sprintf ("AWS_DEFAULT_REGION=%s" , v ))
195
+ }
196
+
197
+ envPrep .Files [credentialFilePath ] = credentialFile
198
+
199
+ envPrep .CmdEnvs = append (envPrep .CmdEnvs , "AWS_EC2_METADATA_DISABLED=true" ) // https://github.com/aws/aws-cli/issues/5262#issuecomment-705832151
200
+ envPrep .CmdEnvs = append (envPrep .CmdEnvs , fmt .Sprintf ("AWS_SHARED_CREDENTIALS_FILE=%s" , credentialFilePath ))
201
+
202
+ case ConnectionTypeAzure :
203
+ args := []string {"login" , "--service-principal" , "--username" , c .Username , "--password" , c .Password }
204
+ if v , ok := c .Properties ["tenant" ]; ok {
205
+ args = append (args , "--tenant" )
206
+ args = append (args , v )
207
+ }
208
+
209
+ // login with service principal
210
+ envPrep .PreRuns = append (envPrep .PreRuns , exec .CommandContext (ctx , "az" , args ... ))
211
+
212
+ case ConnectionTypeGCP :
213
+ var credentialFile bytes.Buffer
214
+ credentialFile .WriteString (c .Certificate )
215
+
216
+ // credentialFilePath := "$HOME/.config/gcloud/credentials"
217
+ credentialFilePath := filepath .Join (".creds" , "gcp" , fmt .Sprintf ("cred-%d" , rand .Intn (100000000 )))
218
+
219
+ // to configure gcloud CLI to use the service account specified in GOOGLE_APPLICATION_CREDENTIALS,
220
+ // we need to explicitly activate it
221
+ envPrep .PreRuns = append (envPrep .PreRuns , exec .CommandContext (ctx , "gcloud" , "auth" , "activate-service-account" , "--key-file" , credentialFilePath ))
222
+ envPrep .Files [credentialFilePath ] = credentialFile
223
+
224
+ envPrep .CmdEnvs = append (envPrep .CmdEnvs , fmt .Sprintf ("GOOGLE_APPLICATION_CREDENTIALS=%s" , credentialFilePath ))
225
+ }
226
+
227
+ return envPrep
228
+ }
229
+
230
+ type EnvPrep struct {
231
+ // Env is the connection credentials in environment variables
232
+ Env []string
233
+
234
+ // CmdEnvs is a list of env vars that will be passed to the command
235
+ CmdEnvs []string
236
+
237
+ // List of commands that need to be run before the actual command.
238
+ // These commands will setup the connection.
239
+ PreRuns []* exec.Cmd
240
+
241
+ // File contains the content of the configuration file based on the connection
242
+ Files map [string ]bytes.Buffer
243
+ }
244
+
245
+ // Inject creates the config file & injects the necessary environment variable into the command
246
+ func (c * EnvPrep ) Inject (ctx context.Context , cmd * exec.Cmd ) ([]* exec.Cmd , error ) {
247
+ for path , file := range c .Files {
248
+ if err := saveConfig (file .Bytes (), path ); err != nil {
249
+ return nil , fmt .Errorf ("error saving config to %s: %w" , path , err )
250
+ }
251
+ }
252
+
253
+ cmd .Env = append (cmd .Env , c .CmdEnvs ... )
254
+
255
+ return c .PreRuns , nil
256
+ }
257
+
258
+ func saveConfig (content []byte , absPath string ) error {
259
+ file , err := os .Create (absPath )
260
+ if err != nil {
261
+ return err
262
+ }
263
+ defer file .Close ()
264
+
265
+ _ , err = file .Write (content )
266
+ return err
267
+ }
0 commit comments