Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(COR-981): add admin command to manage JWT for internal usage #392

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ import (
)

var (
jwtKid string
clusterId string
projectId string
lockReason string
orgaErr error
dryRun bool
version string
versionErr error
ageInDay int
execId string
directory string
adminCmd = &cobra.Command{Use: "admin", Hidden: true}
jwtKid string
clusterId string
organizationId string
projectId string
lockReason string
orgaErr error
dryRun bool
version string
versionErr error
ageInDay int
execId string
directory string
rootDns string
additionalClaims string
description string
adminCmd = &cobra.Command{Use: "admin", Hidden: true}
)

func init() {
Expand Down
114 changes: 114 additions & 0 deletions cmd/admin_jw_qovery_usage_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package cmd

import (
"bytes"
"fmt"
"github.com/go-jose/go-jose/v4/json"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"io"
"net/http"
"os"
"text/tabwriter"

"github.com/qovery/qovery-cli/utils"
)

var (
adminJwtForQoveryUsageCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a Jwt for Qovery usage",
Run: func(cmd *cobra.Command, args []string) {
createJwtForQoveryUsage()
},
}
)

func init() {
adminJwtForQoveryUsageCreateCmd.Flags().StringVarP(&clusterId, "cluster-id", "c", "", "Cluster's id")
adminJwtForQoveryUsageCreateCmd.Flags().StringVarP(&organizationId, "organization-id", "", "", "Organization's id")
adminJwtForQoveryUsageCreateCmd.Flags().StringVarP(&rootDns, "root-dns", "", "", "root dns")
adminJwtForQoveryUsageCreateCmd.Flags().StringVarP(&additionalClaims, "additional-claims", "", "{}", "Additional claims in JSON format (e.g., '{\"key1\":\"value1\",\"key2\":\"value2\"}')")
adminJwtForQoveryUsageCreateCmd.Flags().StringVarP(&description, "description", "d", "", "Description of the JWT")

adminJwtForQoveryUsageCmd.AddCommand(adminJwtForQoveryUsageCreateCmd)
}

func createJwtForQoveryUsage() {
utils.CheckAdminUrl()

tokenType, token, err := utils.GetAccessToken()
if err != nil {
utils.PrintlnError(err)
os.Exit(0)
}

var claimsMap map[string]string
err = json.Unmarshal([]byte(additionalClaims), &claimsMap)
if err != nil {
fmt.Printf("Error when parsing additional-claims : %v\n", err)
return
}

type Payload struct {
OrganizationId string `json:"organization_id"`
ClusterId string `json:"cluster_id"`
RootDns string `json:"root_dns"`
AdditionalClaims map[string]string `json:"additional_claims"`
Description string `json:"description"`
}

var payload, _ = json.Marshal(Payload{
ClusterId: clusterId,
OrganizationId: organizationId,
RootDns: rootDns,
AdditionalClaims: claimsMap,
Description: description,
})

url := fmt.Sprintf("%s/jwts", utils.AdminUrl)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", utils.GetAuthorizationHeaderValue(tokenType, token))
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}

body, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
utils.PrintlnError(fmt.Errorf("error uploading debug logs: %s %s", res.Status, body))
return
}

jwtForQoveryUsage := struct {
KeyId string `json:"key_id"`
Description string `json:"description"`
Jwt string `json:"decrypted_jwt"`
CreatedAt string `json:"created_at"`
}{}

if err := json.Unmarshal(body, &jwtForQoveryUsage); err != nil {
log.Fatal(err)
}
_, jwtPayload, err := DecodeJWT(jwtForQoveryUsage.Jwt)
if err != nil {
log.Fatal(err)
}

w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)

_, _ = fmt.Fprintln(w, "Field\t | Value")
_, _ = fmt.Fprintln(w, "------\t | ------")

_, _ = fmt.Fprintf(w, "key_id\t | %s\n", jwtForQoveryUsage.KeyId)
_, _ = fmt.Fprintf(w, "description\t | %s\n", jwtForQoveryUsage.Description)
_, _ = fmt.Fprintf(w, "jwt payload\t | %s\n", jwtPayload)
_, _ = fmt.Fprintf(w, "jwt\t | %s\n", jwtForQoveryUsage.Jwt)
_, _ = fmt.Fprintf(w, "created_at\t | %s\n", jwtForQoveryUsage.CreatedAt)
_ = w.Flush()
}
61 changes: 61 additions & 0 deletions cmd/admin_jw_qovery_usage_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package cmd

import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"net/http"
"os"

"github.com/qovery/qovery-cli/utils"
)

var (
adminJwtForQoveryUsageDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a Jwt for Qovery Usage",
Run: func(cmd *cobra.Command, args []string) {
deleteJwtForQoveryUsage()
},
}
)

func init() {
adminJwtForQoveryUsageDeleteCmd.Flags().StringVarP(&jwtKid, "kid", "", "", "Cluster's id")

adminJwtForQoveryUsageCmd.AddCommand(adminJwtForQoveryUsageDeleteCmd)

}

func deleteJwtForQoveryUsage() {
utils.CheckAdminUrl()

tokenType, token, err := utils.GetAccessToken()
if err != nil {
utils.PrintlnError(err)
os.Exit(0)
}

url := fmt.Sprintf("%s/jwts/%s", utils.AdminUrl, jwtKid)
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", utils.GetAuthorizationHeaderValue(tokenType, token))
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if res == nil {
utils.PrintlnError(fmt.Errorf("error sending delete HTTP request"))
return
}

if res.StatusCode != http.StatusNoContent {
utils.PrintlnError(fmt.Errorf("error: %s", res.Status))
return
}

if err != nil {
log.Fatal(err)
}
}
112 changes: 112 additions & 0 deletions cmd/admin_jw_qovery_usage_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt/v5"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"io"
"net/http"
"os"
"text/tabwriter"

"github.com/qovery/qovery-cli/utils"
)

var (
adminJwtForQoveryUsageListCmd = &cobra.Command{
Use: "list",
Short: "List Jwt for Qovery usage",
Run: func(cmd *cobra.Command, args []string) {
listJwtsForQoveryUsage()
},
}
)

func init() {
adminJwtForQoveryUsageListCmd.Flags()

adminJwtForQoveryUsageCmd.AddCommand(adminJwtForQoveryUsageListCmd)

}

func listJwtsForQoveryUsage() {
utils.CheckAdminUrl()

tokenType, token, err := utils.GetAccessToken()
if err != nil {
utils.PrintlnError(err)
os.Exit(0)
}

url := fmt.Sprintf("%s/jwts", utils.AdminUrl)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", utils.GetAuthorizationHeaderValue(tokenType, token))
req.Header.Set("Content-Type", "application/json")

res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}

body, _ := io.ReadAll(res.Body)
if res.StatusCode != http.StatusOK {
utils.PrintlnError(fmt.Errorf("error uploading debug logs: %s %s", res.Status, body))
return
}

resp := struct {
Results []struct {
KeyId string `json:"key_id"`
Description string `json:"description"`
Jwt string `json:"decrypted_jwt"`
CreatedAt string `json:"created_at"`
} `json:"results"`
}{}
if err := json.Unmarshal(body, &resp); err != nil {
log.Fatal(err)
}

w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
format := "%s\t | %s\t | %s\t | %s\t | %s\n"
_, _ = fmt.Fprintf(w, format, "", "key_id", "descripton", "jwt payload", "created_at")
for idx, jwtForQoveryUsage := range resp.Results {
_, jwtPayload, err := DecodeJWT(jwtForQoveryUsage.Jwt)
if err != nil {
log.Fatal(err)
}
_, _ = fmt.Fprintln(w, "Field\t | Value")
_, _ = fmt.Fprintln(w, "------\t | ------")

_, _ = fmt.Fprintf(w, "index\t | %s\n", fmt.Sprintf("%d", idx+1))
_, _ = fmt.Fprintf(w, "key_id\t | %s\n", jwtForQoveryUsage.KeyId)
_, _ = fmt.Fprintf(w, "description\t | %s\n", jwtForQoveryUsage.Description)
_, _ = fmt.Fprintf(w, "jwt payload\t | %s\n", jwtPayload)
_, _ = fmt.Fprintf(w, "jwt\t | %s\n", jwtForQoveryUsage.Jwt)
_, _ = fmt.Fprintf(w, "created_at\t | %s\n", jwtForQoveryUsage.CreatedAt)
}
_ = w.Flush()
}

func DecodeJWT(tokenString string) (string, string, error) {
token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
if err != nil {
return "", "", fmt.Errorf("failed to parse token: %w", err)
}

headerJSON, err := json.Marshal(token.Header)
if err != nil {
return "", "", fmt.Errorf("failed to marshal header: %w", err)
}

claimsJSON, err := json.Marshal(token.Claims)
if err != nil {
return "", "", fmt.Errorf("failed to marshal claims: %w", err)
}

return string(headerJSON), string(claimsJSON), nil
}
2 changes: 1 addition & 1 deletion cmd/admin_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
var (
adminJwtCmd = &cobra.Command{
Use: "jwt",
Short: "Manage clusters",
Short: "Manage JWT associated to clusters",
Run: func(cmd *cobra.Command, args []string) {
utils.Capture(cmd)

Expand Down
28 changes: 28 additions & 0 deletions cmd/admin_jwt_qovery_usage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"os"

"github.com/spf13/cobra"

"github.com/qovery/qovery-cli/utils"
)

var (
adminJwtForQoveryUsageCmd = &cobra.Command{
Use: "jwt-qovery-usage",
Short: "Manage JWT for qovery usage ",
Run: func(cmd *cobra.Command, args []string) {
utils.Capture(cmd)

if len(args) == 0 {
_ = cmd.Help()
os.Exit(0)
}
},
}
)

func init() {
adminCmd.AddCommand(adminJwtForQoveryUsageCmd)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
Expand Down
Loading