From ff05652956161dd94aa109e2c5d40bd82d4cfd5d Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Wed, 24 Nov 2021 10:27:36 -0800 Subject: Move credential rendering to server --- app/controllers/api.go | 7 +++- app/controllers/api_credentials.go | 84 ++++++++++++++++++++++++++++++++++---- templates/assets/site.js | 18 ++++---- 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 ( ) const ( - contentTypeV1 = "application/vnd.broker.v1+json" // Original type - contentTypeV2 = "application/vnd.broker.v2+json" // Start of migration to multi-cloud + contentTypeV1 = "application/vnd.broker.v1+json" // Original type + contentTypeV2 = "application/vnd.broker.v2+json" // Start of migration to multi-cloud + contentTypeV2AWSBash = "application/vnd.broker.v2.credential.aws.sh" // Bash Formatted Credential for AWS + contentTypeV2AWSPowershell = "application/vnd.broker.v2.credential.aws.psl" // Powershell Formatted Credential for AWS + contentTypeV2AWSConfig = "application/vnd.broker.v2.credential.aws.ini" // INI Formatted Credential for AWS ) 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 @@ package controllers import ( + "bytes" "net/http" + "text/template" "time" "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" @@ -23,8 +25,33 @@ type jsonCredential struct { SecretAccessKey *string `json:"secret_key"` SessionToken *string `json:"session_token"` Expiration *time.Time `json:"expiration"` + ShortName string `json:"-"` } +var ( + bashTemplate = template.Must(template.New("").Parse( + `export AWS_CREDS_EXPIRATION="{{ .Expiration }}" +export AWS_ACCESS_KEY_ID="{{ .AccessKeyId }}" +export AWS_SECRET_ACCESS_KEY="{{ .SecretAccessKey }}" +export AWS_SESSION_TOKEN="{{ .SessionToken }}" +`)) + + pslTemplate = template.Must(template.New("").Parse( + `Set-Item -path env:AWS_CREDS_EXPIRATION -value '{{ .Expiration }}' +Set-Item -path env:AWS_ACCESS_KEY_ID -value '{{ .AccessKeyId }}' +Set-Item -path env:AWS_SECRET_ACCESS_KEY -value '{{ .SecretAccessKey }}' +Set-Item -path env:AWS_SESSION_TOKEN -value '{{ .SessionToken }}' +`)) + + iniTemplate = template.Must(template.New("").Parse( + `[profile {{ .ShortName }}] +aws_access_key_id={{ .AccessKeyId }} +aws_secret_access_key={{ .SecretAccessKey }} +aws_session_token={{ .SessionToken }} +expiration={{ .Expiration }} +`)) +) + type APICredentialsHandler struct { *AWSAPI } @@ -32,29 +59,32 @@ type APICredentialsHandler struct { func NewAPICredentialsHandler(a *AWSAPI) echo.HandlerFunc { al := &APICredentialsHandler{a} h := &controller.ContentTypeNegotiatingHandler{ - DefaultHandler: al.Handle, + DefaultHandler: al.HandleJSONV1, Handlers: map[string]echo.HandlerFunc{ - contentTypeV1: al.Handle, - contentTypeV2: al.Handle, + contentTypeV1: al.HandleJSONV1, + contentTypeV2: al.HandleJSONV1, + contentTypeV2AWSBash: al.HandleBashV2, + contentTypeV2AWSPowershell: al.HandlePSLV2, + contentTypeV2AWSConfig: al.HandleINIV2, }, } return h.Handle } -func (h *APICredentialsHandler) Handle(c echo.Context) error { +func (h *APICredentialsHandler) getAWSCredential(c echo.Context) (*jsonCredential, error) { rc, err := h.GetContext(c) // Does authorization checks if err != nil { - return err + return nil, err } region := c.Param("region") creds, err := rc.AWS.AssumeRole(rc.Principal.Username, ®ion) if err != nil { if aws.IsRegionNotExist(err) { - return echo.NotFoundHandler(c) + return nil, echo.NotFoundHandler(c) } c.Logger().Errorf("Error retrieving credentials: %w", err) - return echo.ErrInternalServerError + return nil, echo.ErrInternalServerError } c.Logger().Infof( @@ -68,10 +98,46 @@ func (h *APICredentialsHandler) Handle(c echo.Context) error { c.Response().Header().Set("Expires", creds.Expiration.Add(-5*time.Minute).Format(time.RFC1123)) - return c.JSON(http.StatusOK, &jsonCredential{ + return &jsonCredential{ AccessKeyId: creds.AccessKeyId, SecretAccessKey: creds.SecretAccessKey, SessionToken: creds.SessionToken, Expiration: creds.Expiration, - }) + ShortName: rc.Account.ShortName, + }, nil +} + +func (h *APICredentialsHandler) renderTemplate(c echo.Context, t *template.Template, ct string) error { + creds, err := h.getAWSCredential(c) + if err != nil { + return err + } + + buf := &bytes.Buffer{} + if err = t.Execute(buf, creds); err != nil { + return echo.ErrInternalServerError + } + + return c.Blob(http.StatusOK, ct, buf.Bytes()) +} + +func (h *APICredentialsHandler) HandleJSONV1(c echo.Context) error { + creds, err := h.getAWSCredential(c) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, creds) +} + +func (h *APICredentialsHandler) HandleBashV2(c echo.Context) error { + return h.renderTemplate(c, bashTemplate, contentTypeV2AWSBash) +} + +func (h *APICredentialsHandler) HandlePSLV2(c echo.Context) error { + return h.renderTemplate(c, pslTemplate, contentTypeV2AWSPowershell) +} + +func (h *APICredentialsHandler) HandleINIV2(c echo.Context) error { + return h.renderTemplate(c, iniTemplate, contentTypeV2AWSConfig) } 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) { event.preventDefault(); var thisRow = event.target.parentElement.parentElement; - var template = event.target.getAttribute("data-template"); - var account = thisRow.getAttribute("data-account-name"); - var credentialEndpoint = thisRow.getAttribute("data-global-credential-endpoint"); + var account = thisRow.dataset["accountName"]; var oldText = event.target.text; var existingTr = document.getElementById("credentials-for-" + account); @@ -41,12 +39,14 @@ function accountTableLinkClick(event) { event.target.text = "Loading..."; - fetch(credentialEndpoint).then(getJSON).then(function(vals) { - vals["ShortName"] = account; - + fetch(thisRow.dataset["globalCredentialEndpoint"], { + "headers": { + "Accept": event.target.dataset["contentType"] + } + }).then(r => r.text()).then(function(text) { var newTr = fillTemplate("credential_row_template", { "Account": account, - "Content": fillTemplate(template, vals) + "Content": text, }); event.target.text = oldText; @@ -69,8 +69,8 @@ function populateAccountRow(row) { var out = fillTemplate("account_row_template", row); document.querySelector("#account-table tr").insertAdjacentHTML("afterend", out); - document.querySelectorAll("#account-row-" + row["short_name"] + " a[data-template]").forEach(function(element) { - element.addEventListener("click", accountTableLinkClick); + document.querySelectorAll(".account-row a[data-content-type]").forEach(function(e) { + e.addEventListener("click", accountTableLinkClick); }); } 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 @@ Select Account - - -