diff options
author | Mike Crute <mike@crute.us> | 2021-11-24 10:27:36 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2021-11-24 10:28:20 -0800 |
commit | ff05652956161dd94aa109e2c5d40bd82d4cfd5d (patch) | |
tree | 67a9e756e64a1e42538e25e46da92c3dd29ba23c | |
parent | 7ba9e94bae1cbeba7fc7e390d09e2821ba46b996 (diff) | |
download | cloud-identity-broker-ff05652956161dd94aa109e2c5d40bd82d4cfd5d.tar.bz2 cloud-identity-broker-ff05652956161dd94aa109e2c5d40bd82d4cfd5d.tar.xz cloud-identity-broker-ff05652956161dd94aa109e2c5d40bd82d4cfd5d.zip |
Move credential rendering to server
-rw-r--r-- | app/controllers/api.go | 7 | ||||
-rw-r--r-- | app/controllers/api_credentials.go | 84 | ||||
-rw-r--r-- | templates/assets/site.js | 18 | ||||
-rw-r--r-- | templates/index.tpl | 27 |
4 files changed, 93 insertions, 43 deletions
diff --git a/app/controllers/api.go b/app/controllers/api.go index 5ee6591..39fc227 100644 --- a/app/controllers/api.go +++ b/app/controllers/api.go | |||
@@ -10,8 +10,11 @@ import ( | |||
10 | ) | 10 | ) |
11 | 11 | ||
12 | const ( | 12 | const ( |
13 | contentTypeV1 = "application/vnd.broker.v1+json" // Original type | 13 | contentTypeV1 = "application/vnd.broker.v1+json" // Original type |
14 | contentTypeV2 = "application/vnd.broker.v2+json" // Start of migration to multi-cloud | 14 | contentTypeV2 = "application/vnd.broker.v2+json" // Start of migration to multi-cloud |
15 | contentTypeV2AWSBash = "application/vnd.broker.v2.credential.aws.sh" // Bash Formatted Credential for AWS | ||
16 | contentTypeV2AWSPowershell = "application/vnd.broker.v2.credential.aws.psl" // Powershell Formatted Credential for AWS | ||
17 | contentTypeV2AWSConfig = "application/vnd.broker.v2.credential.aws.ini" // INI Formatted Credential for AWS | ||
15 | ) | 18 | ) |
16 | 19 | ||
17 | func APIIndexHandler(c echo.Context) error { | 20 | func APIIndexHandler(c echo.Context) error { |
diff --git a/app/controllers/api_credentials.go b/app/controllers/api_credentials.go index cd1a912..d53565e 100644 --- a/app/controllers/api_credentials.go +++ b/app/controllers/api_credentials.go | |||
@@ -1,7 +1,9 @@ | |||
1 | package controllers | 1 | package controllers |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | ||
4 | "net/http" | 5 | "net/http" |
6 | "text/template" | ||
5 | "time" | 7 | "time" |
6 | 8 | ||
7 | "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" | 9 | "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" |
@@ -23,8 +25,33 @@ type jsonCredential struct { | |||
23 | SecretAccessKey *string `json:"secret_key"` | 25 | SecretAccessKey *string `json:"secret_key"` |
24 | SessionToken *string `json:"session_token"` | 26 | SessionToken *string `json:"session_token"` |
25 | Expiration *time.Time `json:"expiration"` | 27 | Expiration *time.Time `json:"expiration"` |
28 | ShortName string `json:"-"` | ||
26 | } | 29 | } |
27 | 30 | ||
31 | var ( | ||
32 | bashTemplate = template.Must(template.New("").Parse( | ||
33 | `export AWS_CREDS_EXPIRATION="{{ .Expiration }}" | ||
34 | export AWS_ACCESS_KEY_ID="{{ .AccessKeyId }}" | ||
35 | export AWS_SECRET_ACCESS_KEY="{{ .SecretAccessKey }}" | ||
36 | export AWS_SESSION_TOKEN="{{ .SessionToken }}" | ||
37 | `)) | ||
38 | |||
39 | pslTemplate = template.Must(template.New("").Parse( | ||
40 | `Set-Item -path env:AWS_CREDS_EXPIRATION -value '{{ .Expiration }}' | ||
41 | Set-Item -path env:AWS_ACCESS_KEY_ID -value '{{ .AccessKeyId }}' | ||
42 | Set-Item -path env:AWS_SECRET_ACCESS_KEY -value '{{ .SecretAccessKey }}' | ||
43 | Set-Item -path env:AWS_SESSION_TOKEN -value '{{ .SessionToken }}' | ||
44 | `)) | ||
45 | |||
46 | iniTemplate = template.Must(template.New("").Parse( | ||
47 | `[profile {{ .ShortName }}] | ||
48 | aws_access_key_id={{ .AccessKeyId }} | ||
49 | aws_secret_access_key={{ .SecretAccessKey }} | ||
50 | aws_session_token={{ .SessionToken }} | ||
51 | expiration={{ .Expiration }} | ||
52 | `)) | ||
53 | ) | ||
54 | |||
28 | type APICredentialsHandler struct { | 55 | type APICredentialsHandler struct { |
29 | *AWSAPI | 56 | *AWSAPI |
30 | } | 57 | } |
@@ -32,29 +59,32 @@ type APICredentialsHandler struct { | |||
32 | func NewAPICredentialsHandler(a *AWSAPI) echo.HandlerFunc { | 59 | func NewAPICredentialsHandler(a *AWSAPI) echo.HandlerFunc { |
33 | al := &APICredentialsHandler{a} | 60 | al := &APICredentialsHandler{a} |
34 | h := &controller.ContentTypeNegotiatingHandler{ | 61 | h := &controller.ContentTypeNegotiatingHandler{ |
35 | DefaultHandler: al.Handle, | 62 | DefaultHandler: al.HandleJSONV1, |
36 | Handlers: map[string]echo.HandlerFunc{ | 63 | Handlers: map[string]echo.HandlerFunc{ |
37 | contentTypeV1: al.Handle, | 64 | contentTypeV1: al.HandleJSONV1, |
38 | contentTypeV2: al.Handle, | 65 | contentTypeV2: al.HandleJSONV1, |
66 | contentTypeV2AWSBash: al.HandleBashV2, | ||
67 | contentTypeV2AWSPowershell: al.HandlePSLV2, | ||
68 | contentTypeV2AWSConfig: al.HandleINIV2, | ||
39 | }, | 69 | }, |
40 | } | 70 | } |
41 | return h.Handle | 71 | return h.Handle |
42 | } | 72 | } |
43 | 73 | ||
44 | func (h *APICredentialsHandler) Handle(c echo.Context) error { | 74 | func (h *APICredentialsHandler) getAWSCredential(c echo.Context) (*jsonCredential, error) { |
45 | rc, err := h.GetContext(c) // Does authorization checks | 75 | rc, err := h.GetContext(c) // Does authorization checks |
46 | if err != nil { | 76 | if err != nil { |
47 | return err | 77 | return nil, err |
48 | } | 78 | } |
49 | 79 | ||
50 | region := c.Param("region") | 80 | region := c.Param("region") |
51 | creds, err := rc.AWS.AssumeRole(rc.Principal.Username, ®ion) | 81 | creds, err := rc.AWS.AssumeRole(rc.Principal.Username, ®ion) |
52 | if err != nil { | 82 | if err != nil { |
53 | if aws.IsRegionNotExist(err) { | 83 | if aws.IsRegionNotExist(err) { |
54 | return echo.NotFoundHandler(c) | 84 | return nil, echo.NotFoundHandler(c) |
55 | } | 85 | } |
56 | c.Logger().Errorf("Error retrieving credentials: %w", err) | 86 | c.Logger().Errorf("Error retrieving credentials: %w", err) |
57 | return echo.ErrInternalServerError | 87 | return nil, echo.ErrInternalServerError |
58 | } | 88 | } |
59 | 89 | ||
60 | c.Logger().Infof( | 90 | c.Logger().Infof( |
@@ -68,10 +98,46 @@ func (h *APICredentialsHandler) Handle(c echo.Context) error { | |||
68 | 98 | ||
69 | c.Response().Header().Set("Expires", creds.Expiration.Add(-5*time.Minute).Format(time.RFC1123)) | 99 | c.Response().Header().Set("Expires", creds.Expiration.Add(-5*time.Minute).Format(time.RFC1123)) |
70 | 100 | ||
71 | return c.JSON(http.StatusOK, &jsonCredential{ | 101 | return &jsonCredential{ |
72 | AccessKeyId: creds.AccessKeyId, | 102 | AccessKeyId: creds.AccessKeyId, |
73 | SecretAccessKey: creds.SecretAccessKey, | 103 | SecretAccessKey: creds.SecretAccessKey, |
74 | SessionToken: creds.SessionToken, | 104 | SessionToken: creds.SessionToken, |
75 | Expiration: creds.Expiration, | 105 | Expiration: creds.Expiration, |
76 | }) | 106 | ShortName: rc.Account.ShortName, |
107 | }, nil | ||
108 | } | ||
109 | |||
110 | func (h *APICredentialsHandler) renderTemplate(c echo.Context, t *template.Template, ct string) error { | ||
111 | creds, err := h.getAWSCredential(c) | ||
112 | if err != nil { | ||
113 | return err | ||
114 | } | ||
115 | |||
116 | buf := &bytes.Buffer{} | ||
117 | if err = t.Execute(buf, creds); err != nil { | ||
118 | return echo.ErrInternalServerError | ||
119 | } | ||
120 | |||
121 | return c.Blob(http.StatusOK, ct, buf.Bytes()) | ||
122 | } | ||
123 | |||
124 | func (h *APICredentialsHandler) HandleJSONV1(c echo.Context) error { | ||
125 | creds, err := h.getAWSCredential(c) | ||
126 | if err != nil { | ||
127 | return err | ||
128 | } | ||
129 | |||
130 | return c.JSON(http.StatusOK, creds) | ||
131 | } | ||
132 | |||
133 | func (h *APICredentialsHandler) HandleBashV2(c echo.Context) error { | ||
134 | return h.renderTemplate(c, bashTemplate, contentTypeV2AWSBash) | ||
135 | } | ||
136 | |||
137 | func (h *APICredentialsHandler) HandlePSLV2(c echo.Context) error { | ||
138 | return h.renderTemplate(c, pslTemplate, contentTypeV2AWSPowershell) | ||
139 | } | ||
140 | |||
141 | func (h *APICredentialsHandler) HandleINIV2(c echo.Context) error { | ||
142 | return h.renderTemplate(c, iniTemplate, contentTypeV2AWSConfig) | ||
77 | } | 143 | } |
diff --git a/templates/assets/site.js b/templates/assets/site.js index 485a4cd..ebf9f5f 100644 --- a/templates/assets/site.js +++ b/templates/assets/site.js | |||
@@ -29,9 +29,7 @@ function accountTableLinkClick(event) { | |||
29 | event.preventDefault(); | 29 | event.preventDefault(); |
30 | 30 | ||
31 | var thisRow = event.target.parentElement.parentElement; | 31 | var thisRow = event.target.parentElement.parentElement; |
32 | var template = event.target.getAttribute("data-template"); | 32 | var account = thisRow.dataset["accountName"]; |
33 | var account = thisRow.getAttribute("data-account-name"); | ||
34 | var credentialEndpoint = thisRow.getAttribute("data-global-credential-endpoint"); | ||
35 | var oldText = event.target.text; | 33 | var oldText = event.target.text; |
36 | 34 | ||
37 | var existingTr = document.getElementById("credentials-for-" + account); | 35 | var existingTr = document.getElementById("credentials-for-" + account); |
@@ -41,12 +39,14 @@ function accountTableLinkClick(event) { | |||
41 | 39 | ||
42 | event.target.text = "Loading..."; | 40 | event.target.text = "Loading..."; |
43 | 41 | ||
44 | fetch(credentialEndpoint).then(getJSON).then(function(vals) { | 42 | fetch(thisRow.dataset["globalCredentialEndpoint"], { |
45 | vals["ShortName"] = account; | 43 | "headers": { |
46 | 44 | "Accept": event.target.dataset["contentType"] | |
45 | } | ||
46 | }).then(r => r.text()).then(function(text) { | ||
47 | var newTr = fillTemplate("credential_row_template", { | 47 | var newTr = fillTemplate("credential_row_template", { |
48 | "Account": account, | 48 | "Account": account, |
49 | "Content": fillTemplate(template, vals) | 49 | "Content": text, |
50 | }); | 50 | }); |
51 | 51 | ||
52 | event.target.text = oldText; | 52 | event.target.text = oldText; |
@@ -69,8 +69,8 @@ function populateAccountRow(row) { | |||
69 | var out = fillTemplate("account_row_template", row); | 69 | var out = fillTemplate("account_row_template", row); |
70 | document.querySelector("#account-table tr").insertAdjacentHTML("afterend", out); | 70 | document.querySelector("#account-table tr").insertAdjacentHTML("afterend", out); |
71 | 71 | ||
72 | document.querySelectorAll("#account-row-" + row["short_name"] + " a[data-template]").forEach(function(element) { | 72 | document.querySelectorAll(".account-row a[data-content-type]").forEach(function(e) { |
73 | element.addEventListener("click", accountTableLinkClick); | 73 | e.addEventListener("click", accountTableLinkClick); |
74 | }); | 74 | }); |
75 | } | 75 | } |
76 | 76 | ||
diff --git a/templates/index.tpl b/templates/index.tpl index da0b59e..c8c95d4 100644 --- a/templates/index.tpl +++ b/templates/index.tpl | |||
@@ -4,25 +4,6 @@ | |||
4 | <title>Select Account</title> | 4 | <title>Select Account</title> |
5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | 5 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> |
6 | <link rel="stylesheet" type="text/css" href="/assets/site.css" /> | 6 | <link rel="stylesheet" type="text/css" href="/assets/site.css" /> |
7 | <script id="shell_template" type="text/template"> | ||
8 | export AWS_CREDS_EXPIRATION="[[ .expiration ]]" | ||
9 | export AWS_ACCESS_KEY_ID="[[ .access_key ]]" | ||
10 | export AWS_SECRET_ACCESS_KEY="[[ .secret_key ]]" | ||
11 | export AWS_SESSION_TOKEN="[[ .session_token ]]" | ||
12 | </script> | ||
13 | <script id="powershell_template" type="text/template"> | ||
14 | Set-Item -path env:AWS_CREDS_EXPIRATION -value '[[ .expiration ]]' | ||
15 | Set-Item -path env:AWS_ACCESS_KEY_ID -value '[[ .access_key ]]' | ||
16 | Set-Item -path env:AWS_SECRET_ACCESS_KEY -value '[[ .secret_key ]]' | ||
17 | Set-Item -path env:AWS_SESSION_TOKEN -value '[[ .session_token ]]' | ||
18 | </script> | ||
19 | <script id="aws_config_template" type="text/template"> | ||
20 | [profile [[ .ShortName ]]] | ||
21 | aws_access_key_id=[[ .access_key ]] | ||
22 | aws_secret_access_key=[[ .secret_key ]] | ||
23 | aws_session_token=[[ .session_token ]] | ||
24 | expiration=[[ .expiration ]] | ||
25 | </script> | ||
26 | <script id="credential_row_template" type="text/template"> | 7 | <script id="credential_row_template" type="text/template"> |
27 | <tr id="credentials-for-[[ .Account ]]"> | 8 | <tr id="credentials-for-[[ .Account ]]"> |
28 | <td colspan="3"> | 9 | <td colspan="3"> |
@@ -32,14 +13,14 @@ | |||
32 | </tr> | 13 | </tr> |
33 | </script> | 14 | </script> |
34 | <script id="account_row_template" type="text/template"> | 15 | <script id="account_row_template" type="text/template"> |
35 | <tr id="account-row-[[ .short_name ]]" data-account-name="[[ .short_name ]]" data-global-credential-endpoint="[[ .global_credential_url ]]"> | 16 | <tr id="account-row-[[ .short_name ]]" class="account-row" data-account-name="[[ .short_name ]]" data-global-credential-endpoint="[[ .global_credential_url ]]"> |
36 | <td>[[ .name ]]</td> | 17 | <td>[[ .name ]]</td> |
37 | <td>[[ .vendor ]]</td> | 18 | <td>[[ .vendor ]]</td> |
38 | <td> | 19 | <td> |
39 | <a href="[[ .console_redirect_url ]]">Console</a> | | 20 | <a href="[[ .console_redirect_url ]]">Console</a> | |
40 | <a data-template="aws_config_template" href="#/cli/[[ .short_name ]]">AWS CLI</a> | | 21 | <a data-content-type="application/vnd.broker.v2.credential.aws.ini" href="#/cli/[[ .short_name ]]">AWS CLI</a> | |
41 | <a data-template="shell_template" href="#/sh/[[ .short_name ]]">Bash</a> | | 22 | <a data-content-type="application/vnd.broker.v2.credential.aws.sh" href="#/sh/[[ .short_name ]]">Bash</a> | |
42 | <a data-template="powershell_template" href="#/ps/[[ .short_name ]]">Powershell</a> | 23 | <a data-content-type="application/vnd.broker.v2.credential.aws.psl" href="#/ps/[[ .short_name ]]">Powershell</a> |
43 | <span class="admin">| <a>Edit</a></span> | 24 | <span class="admin">| <a>Edit</a></span> |
44 | </td> | 25 | </td> |
45 | </tr> | 26 | </tr> |