Skip to content

Commit e5fe857

Browse files
authored
Merge pull request #1583 from powerkimhub/enhance/kubeconfig-csp-cli-free
[K8S] Support kubeconfig with dynamic token types
2 parents cb78e13 + 737257b commit e5fe857

File tree

19 files changed

+721
-71
lines changed

19 files changed

+721
-71
lines changed

api-runtime/common-runtime/ClusterManager.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,16 @@ func ListCluster(connectionName string, rsType string) ([]*cres.ClusterInfo, err
972972
// set ResourceInfo
973973
info.IId = getUserIID(cres.IID{NameId: iidInfo.NameId, SystemId: iidInfo.SystemId})
974974

975+
// Replace placeholders in kubeconfig with actual values
976+
if info.AccessInfo.Kubeconfig != "" {
977+
if strings.Contains(info.AccessInfo.Kubeconfig, "CONNECTION_NAME_PLACEHOLDER") {
978+
info.AccessInfo.Kubeconfig = strings.ReplaceAll(info.AccessInfo.Kubeconfig, "CONNECTION_NAME_PLACEHOLDER", connectionName)
979+
}
980+
if strings.Contains(info.AccessInfo.Kubeconfig, "CLUSTER_NAME_PLACEHOLDER") {
981+
info.AccessInfo.Kubeconfig = strings.ReplaceAll(info.AccessInfo.Kubeconfig, "CLUSTER_NAME_PLACEHOLDER", iidInfo.NameId)
982+
}
983+
}
984+
975985
// set used Resources's userIID
976986
err = setResourcesNameId(connectionName, &info)
977987
if err != nil {
@@ -1064,6 +1074,16 @@ func GetCluster(connectionName string, rsType string, clusterName string) (*cres
10641074
// set ResourceInfo
10651075
info.IId = getUserIID(cres.IID{NameId: iidInfo.NameId, SystemId: iidInfo.SystemId})
10661076

1077+
// Replace placeholders in kubeconfig with actual values
1078+
if info.AccessInfo.Kubeconfig != "" {
1079+
if strings.Contains(info.AccessInfo.Kubeconfig, "CONNECTION_NAME_PLACEHOLDER") {
1080+
info.AccessInfo.Kubeconfig = strings.ReplaceAll(info.AccessInfo.Kubeconfig, "CONNECTION_NAME_PLACEHOLDER", connectionName)
1081+
}
1082+
if strings.Contains(info.AccessInfo.Kubeconfig, "CLUSTER_NAME_PLACEHOLDER") {
1083+
info.AccessInfo.Kubeconfig = strings.ReplaceAll(info.AccessInfo.Kubeconfig, "CLUSTER_NAME_PLACEHOLDER", clusterName)
1084+
}
1085+
}
1086+
10671087
// set used Resources's userIID
10681088
err = setResourcesNameId(connectionName, &info)
10691089
if err != nil {
@@ -1074,6 +1094,84 @@ func GetCluster(connectionName string, rsType string, clusterName string) (*cres
10741094
return &info, nil
10751095
}
10761096

1097+
// Generate Token for Cluster Authentication
1098+
// (1) get IID(NameId)
1099+
// (2) generate token using driver
1100+
func GenerateClusterToken(connectionName string, clusterName string) (string, error) {
1101+
cblog.Info("call GenerateClusterToken()")
1102+
1103+
// check empty and trim user inputs
1104+
connectionName, err := EmptyCheckAndTrim("connectionName", connectionName)
1105+
if err != nil {
1106+
cblog.Error(err)
1107+
return "", err
1108+
}
1109+
1110+
if err := checkCapability(connectionName, CLUSTER_HANDLER); err != nil {
1111+
return "", err
1112+
}
1113+
1114+
clusterName, err = EmptyCheckAndTrim("clusterName", clusterName)
1115+
if err != nil {
1116+
cblog.Error(err)
1117+
return "", err
1118+
}
1119+
1120+
cldConn, err := ccm.GetCloudConnection(connectionName)
1121+
if err != nil {
1122+
cblog.Error(err)
1123+
return "", err
1124+
}
1125+
1126+
handler, err := cldConn.CreateClusterHandler()
1127+
if err != nil {
1128+
cblog.Error(err)
1129+
return "", err
1130+
}
1131+
1132+
clusterSPLock.RLock(connectionName, clusterName)
1133+
defer clusterSPLock.RUnlock(connectionName, clusterName)
1134+
1135+
// (1) get IID(NameId)
1136+
var iidInfoList []*ClusterIIDInfo
1137+
if os.Getenv("PERMISSION_BASED_CONTROL_MODE") != "" {
1138+
err = getAuthIIDInfoList(connectionName, &iidInfoList)
1139+
if err != nil {
1140+
cblog.Error(err)
1141+
return "", err
1142+
}
1143+
} else {
1144+
err = infostore.ListByCondition(&iidInfoList, CONNECTION_NAME_COLUMN, connectionName)
1145+
if err != nil {
1146+
cblog.Error(err)
1147+
return "", err
1148+
}
1149+
}
1150+
var iidInfo *ClusterIIDInfo
1151+
var bool_ret = false
1152+
for _, OneIIdInfo := range iidInfoList {
1153+
if OneIIdInfo.NameId == clusterName {
1154+
iidInfo = OneIIdInfo
1155+
bool_ret = true
1156+
break
1157+
}
1158+
}
1159+
if bool_ret == false {
1160+
err := fmt.Errorf("The cluster '%s' does not exist!", clusterName)
1161+
cblog.Error(err)
1162+
return "", err
1163+
}
1164+
1165+
// (2) generate token using driver
1166+
token, err := handler.GenerateClusterToken(getDriverIID(cres.IID{NameId: iidInfo.NameId, SystemId: iidInfo.SystemId}))
1167+
if err != nil {
1168+
cblog.Error(err)
1169+
return "", err
1170+
}
1171+
1172+
return token, nil
1173+
}
1174+
10771175
// (1) check exist(NameID)
10781176
// (2) add NodeGroup
10791177
// (3) Get ClusterInfo

api-runtime/rest-runtime/CBSpiderRuntime.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ func RunServer() {
431431
{"GET", "/cluster", ListCluster},
432432
{"GET", "/cluster/:Name", GetCluster},
433433
{"DELETE", "/cluster/:Name", DeleteCluster},
434+
{"GET", "/cluster/:Name/token", GetClusterToken},
434435
//-- for NodeGroup
435436
{"POST", "/cluster/:Name/nodegroup", AddNodeGroup},
436437
{"DELETE", "/cluster/:Name/nodegroup/:NodeGroupName", RemoveNodeGroup},

api-runtime/rest-runtime/ClusterRest.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,3 +742,60 @@ func convertNodeGroup(ngReq ClusterNodeGroupRequest) cres.NodeGroupInfo {
742742
}
743743
return nodeGroupInfo
744744
}
745+
746+
// ClusterTokenResponse represents the response for EKS token
747+
type ClusterTokenResponse struct {
748+
ApiVersion string `json:"apiVersion" example:"client.authentication.k8s.io/v1"`
749+
Kind string `json:"kind" example:"ExecCredential"`
750+
Status ClusterTokenStatus `json:"status"`
751+
}
752+
753+
type ClusterTokenStatus struct {
754+
Token string `json:"token" example:"k8s-aws-v1.aHR0cHM6Ly9zdHMuYXA..."`
755+
}
756+
757+
// getClusterToken godoc
758+
// @ID get-cluster-token
759+
// @Summary Get Cluster Token
760+
// @Description Get a temporary token for accessing EKS cluster (for kubectl exec auth)
761+
// @Tags [Cluster Management]
762+
// @Accept json
763+
// @Produce json
764+
// @Param Name path string true "The name of the Cluster to get token for"
765+
// @Param ConnectionName query string true "The name of the Connection to use"
766+
// @Success 200 {object} ClusterTokenResponse "Temporary token for cluster access"
767+
// @Failure 400 {object} SimpleMsg "Bad Request, missing required parameters"
768+
// @Failure 404 {object} SimpleMsg "Resource Not Found"
769+
// @Failure 500 {object} SimpleMsg "Internal Server Error"
770+
// @Router /cluster/{Name}/token [get]
771+
func GetClusterToken(c echo.Context) error {
772+
cblog.Info("call GetClusterToken()")
773+
774+
// Get parameters from path and query
775+
clusterName := c.Param("Name")
776+
if clusterName == "" {
777+
return echo.NewHTTPError(http.StatusBadRequest, "clusterName is required")
778+
}
779+
780+
connectionName := c.QueryParam("ConnectionName")
781+
if connectionName == "" {
782+
return echo.NewHTTPError(http.StatusBadRequest, "ConnectionName is required")
783+
}
784+
785+
// Get cluster info first to validate cluster exists and get token
786+
token, err := cmrt.GenerateClusterToken(connectionName, clusterName)
787+
if err != nil {
788+
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
789+
}
790+
791+
// Return in kubectl exec credential format
792+
response := ClusterTokenResponse{
793+
ApiVersion: "client.authentication.k8s.io/v1",
794+
Kind: "ExecCredential",
795+
Status: ClusterTokenStatus{
796+
Token: token,
797+
},
798+
}
799+
800+
return c.JSON(http.StatusOK, response)
801+
}

api-runtime/rest-runtime/admin-web/html/cluster.html

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,10 +1894,38 @@ <h2>System ID (Managed by CSP)</h2>
18941894
}
18951895

18961896
function copyKubeconfigToClipboard(button) {
1897-
const kubeconfigText = button.previousElementSibling.textContent; // Grab the kubeconfig value
1897+
let kubeconfigText = button.previousElementSibling.textContent; // Grab the kubeconfig value
1898+
1899+
// Remove leading/trailing whitespace and normalize line breaks
1900+
kubeconfigText = kubeconfigText.trim();
1901+
1902+
// Remove excessive leading whitespace from each line while preserving YAML structure
1903+
const lines = kubeconfigText.split('\n');
1904+
const cleanLines = [];
1905+
let minIndent = Infinity;
1906+
1907+
// Find minimum indentation (excluding empty lines)
1908+
lines.forEach(line => {
1909+
if (line.trim()) {
1910+
const leadingSpaces = line.match(/^\s*/)[0].length;
1911+
minIndent = Math.min(minIndent, leadingSpaces);
1912+
}
1913+
});
1914+
1915+
// Remove the minimum indentation from all lines
1916+
lines.forEach(line => {
1917+
if (line.trim()) {
1918+
cleanLines.push(line.substring(minIndent));
1919+
} else {
1920+
cleanLines.push('');
1921+
}
1922+
});
1923+
1924+
const cleanKubeconfigText = cleanLines.join('\n').trim();
1925+
18981926
const tempInput = document.createElement('textarea'); // Use textarea to handle multi-line text
18991927
document.body.appendChild(tempInput);
1900-
tempInput.value = kubeconfigText; // Set its value to the kubeconfig YAML text
1928+
tempInput.value = cleanKubeconfigText; // Set its value to the cleaned kubeconfig YAML text
19011929
tempInput.select(); // Select the text
19021930
document.execCommand('copy'); // Execute the copy command
19031931
document.body.removeChild(tempInput); // Remove the temporary input element

api/docs.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1481,6 +1481,64 @@ const docTemplate = `{
14811481
}
14821482
}
14831483
},
1484+
"/cluster/{Name}/token": {
1485+
"get": {
1486+
"description": "Get a temporary token for accessing EKS cluster (for kubectl exec auth)",
1487+
"consumes": [
1488+
"application/json"
1489+
],
1490+
"produces": [
1491+
"application/json"
1492+
],
1493+
"tags": [
1494+
"[Cluster Management]"
1495+
],
1496+
"summary": "Get Cluster Token",
1497+
"operationId": "get-cluster-token",
1498+
"parameters": [
1499+
{
1500+
"type": "string",
1501+
"description": "The name of the Cluster to get token for",
1502+
"name": "Name",
1503+
"in": "path",
1504+
"required": true
1505+
},
1506+
{
1507+
"type": "string",
1508+
"description": "The name of the Connection to use",
1509+
"name": "ConnectionName",
1510+
"in": "query",
1511+
"required": true
1512+
}
1513+
],
1514+
"responses": {
1515+
"200": {
1516+
"description": "Temporary token for cluster access",
1517+
"schema": {
1518+
"$ref": "#/definitions/spider.ClusterTokenResponse"
1519+
}
1520+
},
1521+
"400": {
1522+
"description": "Bad Request, missing required parameters",
1523+
"schema": {
1524+
"$ref": "#/definitions/spider.SimpleMsg"
1525+
}
1526+
},
1527+
"404": {
1528+
"description": "Resource Not Found",
1529+
"schema": {
1530+
"$ref": "#/definitions/spider.SimpleMsg"
1531+
}
1532+
},
1533+
"500": {
1534+
"description": "Internal Server Error",
1535+
"schema": {
1536+
"$ref": "#/definitions/spider.SimpleMsg"
1537+
}
1538+
}
1539+
}
1540+
}
1541+
},
14841542
"/cluster/{Name}/upgrade": {
14851543
"put": {
14861544
"description": "Upgrade a Cluster to a specified version.",
@@ -11581,6 +11639,31 @@ const docTemplate = `{
1158111639
}
1158211640
}
1158311641
},
11642+
"spider.ClusterTokenResponse": {
11643+
"type": "object",
11644+
"properties": {
11645+
"apiVersion": {
11646+
"type": "string",
11647+
"example": "client.authentication.k8s.io/v1"
11648+
},
11649+
"kind": {
11650+
"type": "string",
11651+
"example": "ExecCredential"
11652+
},
11653+
"status": {
11654+
"$ref": "#/definitions/spider.ClusterTokenStatus"
11655+
}
11656+
}
11657+
},
11658+
"spider.ClusterTokenStatus": {
11659+
"type": "object",
11660+
"properties": {
11661+
"token": {
11662+
"type": "string",
11663+
"example": "k8s-aws-v1.aHR0cHM6Ly9zdHMuYXA..."
11664+
}
11665+
}
11666+
},
1158411667
"spider.ClusterUpgradeRequest": {
1158511668
"type": "object",
1158611669
"required": [

0 commit comments

Comments
 (0)