-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathaws_consumer.go
156 lines (138 loc) · 4.25 KB
/
aws_consumer.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
package auth
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/sts"
log "github.com/sirupsen/logrus"
"regexp"
"strings"
)
// AwsConsumerInterface encapsulates all actions performs with the AWS services
type AwsConsumerInterface interface {
// ReadConfiguration reads the configured S3 Bucket and refreshes the Config
ReadConfiguration() error
// JwksURL returns the configured JWK url
JwksURL() string
// Rules holds the globals rules loaded from the S3 bucket
Rules() []Rule
// AssumeRole performs this for the give rule
AssumeRole(ctx context.Context, rule *Rule, name string) (*sts.Credentials, error)
// RetrieveRulesFromRoleTags checks whether a string matches the rule format
RetrieveRulesFromRoleTags(ctx context.Context, role string) ([]Rule, error)
// BoundIssuer holds the global issue configuration
BoundIssuer() string
// BoundAudience holds the global audience configuration
BoundAudience() string
}
// AwsConsumer is the implementation of AwsConsumerInterface
type AwsConsumer struct {
AWS AwsServiceWrapperInterface
Config *Config
}
// NewAwsConsumer constructs a new consumer with the proper ServiceWrapper
func NewAwsConsumer(config *Config) (*AwsConsumer, error) {
consumer := &AwsConsumer{
AWS: &AwsServiceWrapper{},
Config: config,
}
if config.Bucket != "" && config.ObjectKey != "" {
err := consumer.ReadConfiguration()
if err != nil {
return nil, err
}
}
return consumer, nil
}
// ReadConfiguration reads the configured S3 Bucket and returns Config
func (a *AwsConsumer) ReadConfiguration() error {
content, err := a.AWS.GetS3Object(a.Config.Bucket, a.Config.ObjectKey)
if err != nil {
return err
}
decoder := json.NewDecoder(content)
if err := decoder.Decode(a.Config); err != nil {
return fmt.Errorf("Unable to read RULES inputClaims.\n Error: %v", err)
}
log.Debugf("Successfully imported config %v", a.Config)
defer content.Close()
return nil
}
func (a *AwsConsumer) SessionName(name string) string {
invalidChars := regexp.MustCompile(`[^[:word:]+=,.@-]`)
name = invalidChars.ReplaceAllLiteralString(name, "")
if len(name) > 64 {
return name[len(name)-64:]
}
return name
}
// AssumeRole performs this for the give rule
func (a *AwsConsumer) AssumeRole(ctx context.Context, rule *Rule, name string) (*sts.Credentials, error) {
duration := rule.Duration
if duration == 0 {
duration = a.Config.Duration
}
sessionName := a.SessionName(name)
roleToAssumeArn := rule.Role
result, err := a.AWS.AssumeRole(&sts.AssumeRoleInput{
RoleArn: &roleToAssumeArn,
RoleSessionName: &sessionName,
DurationSeconds: &duration,
})
if err != nil {
return nil, fmt.Errorf("unable to perform sts.AssumeRole: %w", err)
}
return result.Credentials, nil
}
// RetrieveRulesFromRoleTags checks the IAM role for further rules configured through tags
func (a *AwsConsumer) RetrieveRulesFromRoleTags(ctx context.Context, roleArn string) ([]Rule, error) {
logger := Logger(ctx)
validRole := regexp.MustCompile(`^arn:aws:iam::\d{12}:role/[a-zA-Z0-9-_]+$`)
if !validRole.MatchString(roleArn) {
return nil, fmt.Errorf("invalid role format")
}
logger.Debugf("GetRole %s", roleArn[31:])
result, err := a.AWS.GetRole(&iam.GetRoleInput{
RoleName: aws.String(roleArn[31:]),
})
if err != nil {
return nil, err
}
if !a.Config.RoleAnnotationsEnabled || len(a.Config.RoleAnnotationPrefix) == 0 {
return nil, nil
}
var rules []Rule
for _, tag := range result.Role.Tags {
if !strings.HasPrefix(*tag.Key, a.Config.RoleAnnotationPrefix) {
continue
}
tagDecoded, err := base64.StdEncoding.DecodeString(*tag.Value)
if err != nil {
continue
}
rule := Rule{
Role: roleArn,
Duration: a.Config.Duration,
ClaimValues: tagDecoded,
}
rules = append(rules, rule)
}
return rules, nil
}
// Rules returns the list of claim to role configuration rules
func (a *AwsConsumer) Rules() []Rule {
return a.Config.Rules
}
// JwksURL forwards the url from the configuration
func (a *AwsConsumer) JwksURL() string {
return a.Config.JwksURL
}
func (a *AwsConsumer) BoundIssuer() string {
return a.Config.BoundIssuer
}
func (a *AwsConsumer) BoundAudience() string {
return a.Config.BoundAudience
}