Explorar el Código

rotate aws credentials implemented

Alexander Belanger hace 5 años
padre
commit
cecd6a00d4

+ 89 - 2
dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx

@@ -1,11 +1,37 @@
-import React, { useContext } from 'react'
+import React, { useContext, useState } from 'react'
 import styled from 'styled-components';
 import Heading from 'components/values-form/Heading';
 import Helper from "components/values-form/Helper";
+import InputRow from "components/values-form/InputRow";
 import { Context } from 'shared/Context';
+import api from "shared/api";
 
-export const ClusterSettings = () => {
+const ClusterSettings: React.FC = () => {
   const context = useContext(Context);
+  const [accessKeyId, setAccessKeyId] = useState<string>("");
+  const [secretKey, setSecretKey] = useState<string>("");
+  const [startRotateCreds, setStartRotateCreds] = useState<boolean>(false);
+  const [successfulRotate, setSuccessfulRotate] = useState<boolean>(false);
+
+  let rotateCredentials = () => {
+    api.overwriteAWSIntegration(
+      "<token>",
+      {
+        aws_access_key_id: accessKeyId,
+        aws_secret_access_key: secretKey
+      },
+      {
+        projectID: context.currentProject.id,
+        awsIntegrationID: context.currentCluster.aws_integration_id,
+        cluster_id: context.currentCluster.id,
+      }
+    ).then(({ data }) => {
+      setSuccessfulRotate(true)
+    })
+    .catch(() => {
+      setSuccessfulRotate(false)
+    })
+  }
 
   let helperText = <Helper>
     Delete this cluster and underlying infrastructure. To
@@ -29,9 +55,69 @@ export const ClusterSettings = () => {
     </Helper>
   }
 
+  let keyRotationSection = null
+
+  if (context.currentCluster?.aws_integration_id && context.currentCluster?.aws_integration_id != 0) {
+    if (successfulRotate) {
+      keyRotationSection = <div>
+        <Heading>Credential Rotation</Heading>
+        <Helper>
+          Successfully rotated credentials!
+        </Helper>
+        </div>
+    } else if (startRotateCreds) {
+      keyRotationSection = <div>
+        <Heading>Credential Rotation</Heading>
+        <Helper>
+          Input the new credentials for the EKS cluster. 
+        </Helper>
+        <InputRow
+          type="text"
+          value={accessKeyId}
+          setValue={(x: string) => setAccessKeyId(x)}
+          label="👤 AWS Access ID"
+          placeholder="ex: AKIAIOSFODNN7EXAMPLE"
+          width="100%"
+          isRequired={true}
+        />
+        <InputRow
+          type="password"
+          value={secretKey}
+          setValue={(x: string) => setSecretKey(x)}
+          label="🔒 AWS Secret Key"
+          placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
+          width="100%"
+          isRequired={true}
+        />
+        <Button
+          color="#616FEEcc"
+          onClick={rotateCredentials}
+        >
+          Submit
+        </Button>
+      </div>
+    } else {
+      keyRotationSection = <div>
+        <Heading>Credential Rotation</Heading>
+        <Helper>
+          Rotate the credentials that this cluster uses to connect to the cluster.
+        </Helper>
+        <Button
+          color="#616FEEcc"
+          onClick={() => setStartRotateCreds(true)}
+        >
+          Rotate Credentials
+        </Button>
+        
+      </div>
+    }
+    
+  }
+
   return (
     <div>
       <StyledSettingsSection showSource={false}>
+          {keyRotationSection}
           <Heading>Delete Cluster</Heading>
           {helperText}
           <Button
@@ -45,6 +131,7 @@ export const ClusterSettings = () => {
   )
 }
 
+export default ClusterSettings
 
 const StyledSettingsSection = styled.div<{ showSource: boolean }>`
   margin-top: 35px;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/Dashboard.tsx

@@ -5,7 +5,7 @@ import { Context } from "shared/Context";
 import TabSelector from "components/TabSelector";
 
 import NodeList from "./NodeList";
-import { ClusterSettings } from "./ClusterSettings";
+import ClusterSettings from "./ClusterSettings";
 
 
 type TabEnum = "nodes" | "settings";

+ 4 - 3
dashboard/src/shared/api.tsx

@@ -47,15 +47,16 @@ const createAWSIntegration = baseApi<
 
 const overwriteAWSIntegration = baseApi<
   {
-    aws_access_key_id: string;
-    aws_secret_access_key: string;
+    aws_access_key_id: string,
+    aws_secret_access_key: string,
   },
   { 
     projectID: number,
     awsIntegrationID: number,
+    cluster_id: number,
   }
 >("POST", (pathParams) => {
-  return `/api/projects/${pathParams.projectID}/integrations/aws/${pathParams.awsIntegrationID}/overwrite`;
+  return `/api/projects/${pathParams.projectID}/integrations/aws/${pathParams.awsIntegrationID}/overwrite?cluster_id=${pathParams.cluster_id}`;
 });
 
 const createDOCR = baseApi<

+ 1 - 0
dashboard/src/shared/types.tsx

@@ -5,6 +5,7 @@ export interface ClusterType {
   service_account_id: number;
   infra_id?: number;
   service?: string;
+  aws_integration_id?: number;
 }
 
 export interface DetailedClusterType extends ClusterType {

+ 10 - 6
internal/models/cluster.go

@@ -86,6 +86,9 @@ type ClusterExternal struct {
 
 	// The infra id, if cluster was provisioned with Porter
 	InfraID uint `json:"infra_id"`
+
+	// (optional) The aws integration id, if available
+	AWSIntegrationID uint `json:"aws_integration_id"`
 }
 
 // Externalize generates an external Cluster to be shared over REST
@@ -101,12 +104,13 @@ func (c *Cluster) Externalize() *ClusterExternal {
 	}
 
 	return &ClusterExternal{
-		ID:        c.ID,
-		ProjectID: c.ProjectID,
-		Name:      c.Name,
-		Server:    c.Server,
-		Service:   serv,
-		InfraID:   c.InfraID,
+		ID:               c.ID,
+		ProjectID:        c.ProjectID,
+		Name:             c.Name,
+		Server:           c.Server,
+		Service:          serv,
+		InfraID:          c.InfraID,
+		AWSIntegrationID: c.AWSIntegrationID,
 	}
 }
 

+ 30 - 0
server/api/integration_handler.go

@@ -3,6 +3,7 @@ package api
 import (
 	"encoding/json"
 	"net/http"
+	"net/url"
 	"strconv"
 
 	"github.com/go-chi/chi"
@@ -245,6 +246,35 @@ func (app *App) HandleOverwriteAWSIntegration(w http.ResponseWriter, r *http.Req
 		return
 	}
 
+	// clear the cluster token cache if cluster_id exists
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorDataWrite(err, w)
+		return
+	}
+
+	if len(vals["cluster_id"]) > 0 {
+		clusterID, err := strconv.ParseUint(vals["cluster_id"][0], 10, 64)
+
+		if err != nil {
+			app.handleErrorDataWrite(err, w)
+			return
+		}
+
+		cluster, err := app.Repo.Cluster.ReadCluster(uint(clusterID))
+
+		// clear the token
+		cluster.TokenCache.Token = []byte("")
+
+		cluster, err = app.Repo.Cluster.UpdateClusterTokenCache(&cluster.TokenCache)
+
+		if err != nil {
+			app.handleErrorDataWrite(err, w)
+			return
+		}
+	}
+
 	app.Logger.Info().Msgf("AWS integration overwritten: %d", awsIntegration.ID)
 
 	w.WriteHeader(http.StatusCreated)

+ 8 - 4
server/router/router.go

@@ -697,11 +697,15 @@ func New(a *api.App) *chi.Mux {
 				"POST",
 				"/projects/{project_id}/integrations/aws/{aws_integration_id}/overwrite",
 				auth.DoesUserHaveProjectAccess(
-					auth.DoesUserHaveAWSIntegrationAccess(
-						requestlog.NewHandler(a.HandleOverwriteAWSIntegration, l),
-						mw.URLParam,
+					auth.DoesUserHaveClusterAccess(
+						auth.DoesUserHaveAWSIntegrationAccess(
+							requestlog.NewHandler(a.HandleOverwriteAWSIntegration, l),
+							mw.URLParam,
+							mw.URLParam,
+							false,
+						),
 						mw.URLParam,
-						false,
+						mw.QueryParam,
 					),
 					mw.URLParam,
 					mw.WriteAccess,