Skip to content

Commit

Permalink
Merge pull request #50 from Sushant/mapped-accounts
Browse files Browse the repository at this point in the history
Automap ARNs to user/role for allowed accounts.
  • Loading branch information
nckturner authored Feb 16, 2018
2 parents 5d2b902 + 4fa9e5a commit 2732657
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 1 deletion.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,11 @@ server:
username: alice
groups:
- system:masters
```

# automatically map IAM ARN from these accounts to username.
# NOTE: Always use quotes to avoid the account numbers being recognized as numbers
# instead of strings by the yaml parser.
mapAccounts:
- "012345678901"
- "456789012345"
```
3 changes: 3 additions & 0 deletions cmd/heptio-authenticator-aws/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ func getConfig() (config.Config, error) {
if err := viper.UnmarshalKey("server.mapUsers", &config.UserMappings); err != nil {
logrus.WithError(err).Fatal("invalid server user mappings")
}
if err := viper.UnmarshalKey("server.mapAccounts", &config.AutoMappedAWSAccounts); err != nil {
logrus.WithError(err).Fatal("invalid server account mappings")
}

if config.ClusterID == "" {
return config, errors.New("cluster ID cannot be empty")
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,8 @@ type Config struct {
// UserMappings is a list of mappings from AWS IAM User to
// Kubernetes username + groups.
UserMappings []UserMapping

// AutoMappedAWSAccounts is a list of AWS accounts that are allowed without an explicit user/role mapping.
// IAM ARN from these accounts automatically maps to the Kubernetes username.
AutoMappedAWSAccounts []string
}
12 changes: 12 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type handler struct {
http.ServeMux
lowercaseRoleMap map[string]config.RoleMapping
lowercaseUserMap map[string]config.UserMapping
accountMap map[string]bool
verifier token.Verifier
metrics metrics
}
Expand Down Expand Up @@ -93,6 +94,9 @@ func (c *Server) Run() {
"groups": mapping.Groups,
}).Infof("mapping IAM user")
}
for _, account := range c.AutoMappedAWSAccounts {
logrus.WithField("accountID", account).Infof("mapping IAM Account")
}

// we always listen on localhost (and run with host networking)
listenAddr := fmt.Sprintf("127.0.0.1:%d", c.LocalhostPort)
Expand Down Expand Up @@ -134,6 +138,7 @@ func (c *Server) getHandler() *handler {
h := &handler{
lowercaseRoleMap: make(map[string]config.RoleMapping),
lowercaseUserMap: make(map[string]config.UserMapping),
accountMap: make(map[string]bool),
verifier: token.NewVerifier(c.ClusterID),
metrics: createMetrics(),
}
Expand All @@ -144,6 +149,10 @@ func (c *Server) getHandler() *handler {
h.lowercaseUserMap[strings.ToLower(m.UserARN)] = m
}

for _, m := range c.AutoMappedAWSAccounts {
h.accountMap[m] = true
}

h.HandleFunc("/authenticate", h.authenticateEndpoint)
h.Handle("/metrics", promhttp.Handler())
return h
Expand Down Expand Up @@ -224,6 +233,9 @@ func (h *handler) authenticateEndpoint(w http.ResponseWriter, req *http.Request)
} else if userMapping, exists := h.lowercaseUserMap[arnLower]; exists {
username = userMapping.Username
groups = userMapping.Groups
} else if _, exists := h.accountMap[identity.AccountID]; exists {
groups = []string{}
username = identity.CanonicalARN
} else {
// if the token has a valid signature but the role is not mapped,
// deny with a 403 but print a more useful log message
Expand Down
60 changes: 60 additions & 0 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,63 @@ func TestAuthenticateVerifierUserMapping(t *testing.T) {
verifyAuthResult(t, resp, tokenReview("TestUser", "heptio-authenticator-aws:0123456789012:Test", []string{"sys:admin", "listers"}))
validateMetrics(t, validateOpts{success: 1})
}

func TestAuthenticateVerifierAccountMappingForUser(t *testing.T) {
resp := httptest.NewRecorder()

data, err := json.Marshal(authenticationv1beta1.TokenReview{
Spec: authenticationv1beta1.TokenReviewSpec{
Token: "token",
},
})
if err != nil {
t.Fatalf("Could not marshal in put data: %v", err)
}
req := httptest.NewRequest("POST", "http://k8s.io/authenticate", bytes.NewReader(data))
h := setup(&testVerifier{err: nil, identity: &token.Identity{
ARN: "arn:aws:iam::0123456789012:user/Test",
CanonicalARN: "arn:aws:iam::0123456789012:user/Test",
AccountID: "0123456789012",
UserID: "Test",
SessionName: "",
}})
defer cleanup(h.metrics)
h.accountMap = make(map[string]bool)
h.accountMap["0123456789012"] = true
h.authenticateEndpoint(resp, req)
if resp.Code != http.StatusOK {
t.Errorf("Expected status code %d, was %d", http.StatusOK, resp.Code)
}
verifyAuthResult(t, resp, tokenReview("arn:aws:iam::0123456789012:user/Test", "heptio-authenticator-aws:0123456789012:Test", nil))
validateMetrics(t, validateOpts{success: 1})
}

func TestAuthenticateVerifierAccountMappingForRole(t *testing.T) {
resp := httptest.NewRecorder()

data, err := json.Marshal(authenticationv1beta1.TokenReview{
Spec: authenticationv1beta1.TokenReviewSpec{
Token: "token",
},
})
if err != nil {
t.Fatalf("Could not marshal in put data: %v", err)
}
req := httptest.NewRequest("POST", "http://k8s.io/authenticate", bytes.NewReader(data))
h := setup(&testVerifier{err: nil, identity: &token.Identity{
ARN: "arn:aws:iam::0123456789012:assumed-role/Test/extra",
CanonicalARN: "arn:aws:iam::0123456789012:role/Test",
AccountID: "0123456789012",
UserID: "Test",
SessionName: "",
}})
defer cleanup(h.metrics)
h.accountMap = make(map[string]bool)
h.accountMap["0123456789012"] = true
h.authenticateEndpoint(resp, req)
if resp.Code != http.StatusOK {
t.Errorf("Expected status code %d, was %d", http.StatusOK, resp.Code)
}
verifyAuthResult(t, resp, tokenReview("arn:aws:iam::0123456789012:role/Test", "heptio-authenticator-aws:0123456789012:Test", nil))
validateMetrics(t, validateOpts{success: 1})
}

0 comments on commit 2732657

Please sign in to comment.