Skip to content

Commit

Permalink
feat(COR-981): add admin command to manage JWT for internal usage
Browse files Browse the repository at this point in the history
  • Loading branch information
pggb25 committed Nov 27, 2024
1 parent f14f5dd commit a825002
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 13 deletions.
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

0 comments on commit a825002

Please sign in to comment.