Skip to content

Commit

Permalink
Add CDK_EXTERNAL_USER/CDK_EXTERNAL_PASSWORD to support basic auth for…
Browse files Browse the repository at this point in the history
… API gateways
  • Loading branch information
gbecan committed Dec 5, 2024
1 parent 1c3244b commit a059e92
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 52 deletions.
50 changes: 49 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestApplyShouldWork(t *testing.T) {
if err != nil {
panic(err)
}
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
httpmock.ActivateNonDefault(
client.client.GetClient(),
)
Expand Down Expand Up @@ -77,6 +77,54 @@ func TestApplyShouldWork(t *testing.T) {
}
}

func TestApplyShouldWorkWithExternalUser(t *testing.T) {
defer httpmock.Reset()
baseUrl := "http://baseUrl"
externalUser := "user"
externalPassword := "password"
client, err := Make(ApiParameter{
BaseUrl: baseUrl,
ExternalUser: externalUser,
ExternalPassword: externalPassword,
})
if err != nil {
panic(err)
}
client.setAuthMethodFromEnvIfNeeded()
httpmock.ActivateNonDefault(
client.client.GetClient(),
)
responder := httpmock.NewStringResponder(200, `{"upsertResult": "NotChanged"}`)

topic := resource.Resource{
Json: []byte(`{"yolo": "data"}`),
Kind: "Topic",
Name: "toto",
Version: "v2",
Metadata: map[string]interface{}{
"cluster": "local",
},
}

httpmock.RegisterMatcherResponderWithQuery(
"PUT",
"http://baseUrl/api/public/kafka/v2/cluster/local/topic",
nil,
httpmock.HeaderIs("Authorization", "Basic dXNlcjpwYXNzd29yZA==").
And(httpmock.HeaderIs("X-CDK-CLIENT", "CLI/unknown")).
And(httpmock.BodyContainsBytes(topic.Json)),
responder,
)

body, err := client.Apply(&topic, false)
if err != nil {
t.Error(err)
}
if body != "NotChanged" {
t.Errorf("Bad result expected NotChanged got: %s", body)
}
}

func TestApplyWithDryModeShouldWork(t *testing.T) {
defer httpmock.Reset()
baseUrl := "http://baseUrl"
Expand Down
175 changes: 124 additions & 51 deletions client/console_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client

import (
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand All @@ -14,23 +15,48 @@ import (
"github.com/go-resty/resty/v2"
)

type AuthMethod interface {
AuthorizationHeader() string
}

type BearerToken struct {
Token string
}

func (t BearerToken) AuthorizationHeader() string {
return fmt.Sprintf("Bearer %s", t.Token)
}

type BasicAuth struct {
Username string
Password string
}

func (t BasicAuth) AuthorizationHeader() string {
credentials := fmt.Sprintf("%s:%s", t.Username, t.Password)
encodedCredentials := base64.StdEncoding.EncodeToString([]byte(credentials))
return fmt.Sprintf("Basic %s", encodedCredentials)
}

type Client struct {
apiKey string
baseUrl string
client *resty.Client
kinds schema.KindCatalog
authMethod AuthMethod
baseUrl string
client *resty.Client
kinds schema.KindCatalog
}

type ApiParameter struct {
ApiKey string
BaseUrl string
Debug bool
Key string
Cert string
Cacert string
CdkUser string
CdkPassword string
Insecure bool
ApiKey string
BaseUrl string
Debug bool
Key string
Cert string
Cacert string
CdkUser string
CdkPassword string
ExternalUser string
ExternalPassword string
Insecure bool
}

func uniformizeBaseUrl(baseUrl string) string {
Expand Down Expand Up @@ -60,38 +86,60 @@ func Make(apiParameter ApiParameter) (*Client, error) {
if (apiParameter.CdkUser != "" && apiParameter.CdkPassword == "") || (apiParameter.CdkUser == "" && apiParameter.CdkPassword != "") {
return nil, fmt.Errorf("CDK_USER and CDK_PASSWORD must be provided together")
}

if (apiParameter.ExternalUser != "" && apiParameter.ExternalPassword == "") || (apiParameter.ExternalUser == "" && apiParameter.ExternalPassword != "") {
return nil, fmt.Errorf("CDK_EXTERNAL_USER and CDK_EXTERNAL_PASSWORD must be provided together")
}

if apiParameter.CdkUser != "" && apiParameter.ApiKey != "" {
return nil, fmt.Errorf("Can't set both CDK_USER and CDK_API_KEY")
}

if apiParameter.ExternalUser != "" && apiParameter.ApiKey != "" {
return nil, fmt.Errorf("Can't set both CDK_EXTERNAL_USER and CDK_API_KEY")
}

if apiParameter.CdkUser != "" && apiParameter.ExternalUser != "" {
return nil, fmt.Errorf("Can't set both CDK_USER and CDK_EXTERNAL_USER")
}

if apiParameter.Cacert != "" {
restyClient.SetRootCertificate(apiParameter.Cacert)
}

result := &Client{
apiKey: apiParameter.ApiKey,
baseUrl: uniformizeBaseUrl(apiParameter.BaseUrl),
client: restyClient,
kinds: nil,
authMethod: nil,
baseUrl: uniformizeBaseUrl(apiParameter.BaseUrl),
client: restyClient,
kinds: nil,
}

if apiParameter.Insecure {
result.IgnoreUntrustedCertificate()
}

if apiParameter.ApiKey != "" {
result.authMethod = BearerToken{apiParameter.ApiKey}
}

if apiParameter.CdkUser != "" {
jwtToken, err := result.Login(apiParameter.CdkUser, apiParameter.CdkPassword)
if err != nil {
return nil, fmt.Errorf("Could not login: %s", err)
}
result.apiKey = jwtToken.AccessToken
bearer := BearerToken{jwtToken.AccessToken}
result.authMethod = &bearer
}

if apiParameter.ExternalUser != "" {
result.authMethod = BasicAuth{apiParameter.ExternalUser, apiParameter.ExternalPassword}
}

if result.apiKey != "" {
result.setApiKeyInRestClient()
if result.authMethod != nil {
result.setAuthMethodInRestClient()
} else {
//it will be set later only when really needed
//so aim is not fail when CDK_API_KEY is not set before printing the cmd help
//so aim is not fail when auth method is not set before printing the cmd help
}

err := result.initKindFromApi()
Expand All @@ -105,15 +153,17 @@ func Make(apiParameter ApiParameter) (*Client, error) {

func MakeFromEnv() (*Client, error) {
apiParameter := ApiParameter{
BaseUrl: os.Getenv("CDK_BASE_URL"),
Debug: utils.CdkDebug(),
Key: os.Getenv("CDK_KEY"),
Cert: os.Getenv("CDK_CERT"),
Cacert: os.Getenv("CDK_CACERT"),
ApiKey: os.Getenv("CDK_API_KEY"),
CdkUser: os.Getenv("CDK_USER"),
CdkPassword: os.Getenv("CDK_PASSWORD"),
Insecure: strings.ToLower(os.Getenv("CDK_INSECURE")) == "true",
BaseUrl: os.Getenv("CDK_BASE_URL"),
Debug: utils.CdkDebug(),
Key: os.Getenv("CDK_KEY"),
Cert: os.Getenv("CDK_CERT"),
Cacert: os.Getenv("CDK_CACERT"),
ApiKey: os.Getenv("CDK_API_KEY"),
CdkUser: os.Getenv("CDK_USER"),
CdkPassword: os.Getenv("CDK_PASSWORD"),
ExternalUser: os.Getenv("CDK_EXTERNAL_USER"),
ExternalPassword: os.Getenv("CDK_EXTERNAL_PASSWORD"),
Insecure: strings.ToLower(os.Getenv("CDK_INSECURE")) == "true",
}

client, err := Make(apiParameter)
Expand All @@ -131,25 +181,48 @@ func (c *Client) IgnoreUntrustedCertificate() {
c.client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
}

func (c *Client) setApiKeyFromEnvIfNeeded() {
if c.apiKey == "" {
func (c *Client) setAuthMethodFromEnvIfNeeded() {
if c.authMethod == nil {
apiKey := os.Getenv("CDK_API_KEY")
if apiKey == "" {
fmt.Fprintln(os.Stderr, "Please set CDK_API_KEY")
externalUser := os.Getenv("CDK_EXTERNAL_USER")
externalPassword := os.Getenv("CDK_EXTERNAL_PASSWORD")

if apiKey == "" && externalUser == "" {
fmt.Fprintln(os.Stderr, "Please set CDK_API_KEY or CDK_EXTERNAL_USER/CDK_EXTERNAL_PASSWORD")
os.Exit(1)
}

if apiKey != "" && externalUser != "" {
fmt.Fprintln(os.Stderr, "Can't set both CDK_API_KEY and CDK_EXTERNAL_USER")
os.Exit(1)
}
c.apiKey = apiKey
c.setApiKeyInRestClient()

if externalUser != "" && externalPassword == "" {
fmt.Fprintln(os.Stderr, "Please set CDK_EXTERNAL_PASSWORD when using CDK_EXTERNAL_USER")
os.Exit(1)
}

if apiKey != "" {
c.authMethod = BearerToken{apiKey}
} else {
c.authMethod = BasicAuth{externalUser, externalPassword}
}

c.setAuthMethodInRestClient()
}
}

func (c *Client) setApiKeyInRestClient() {
c.client = c.client.SetHeader("Authorization", "Bearer "+c.apiKey)
func (c *Client) setAuthMethodInRestClient() {
if c.authMethod == nil {
fmt.Fprintln(os.Stderr, "No authentication method defined. Please set CDK_API_KEY or CDK_USER/CDK_PASSWORD or CDK_EXTERNAL_USER/CDK_EXTERNAL_PASSWORD")
os.Exit(1)
}
c.client = c.client.SetHeader("Authorization", c.authMethod.AuthorizationHeader())
}

func (c *Client) SetApiKey(apiKey string) {
c.apiKey = apiKey
c.setApiKeyInRestClient()
c.authMethod = BearerToken{apiKey}
c.setAuthMethodInRestClient()
}

func extractApiError(resp *resty.Response) string {
Expand All @@ -171,7 +244,7 @@ func (client *Client) ActivateDebug() {
}

func (client *Client) Apply(resource *resource.Resource, dryMode bool) (string, error) {
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
if !ok {
Expand Down Expand Up @@ -204,7 +277,7 @@ func (client *Client) Apply(resource *resource.Resource, dryMode bool) (string,

func (client *Client) Get(kind *schema.Kind, parentPathValue []string, queryParams map[string]string) ([]resource.Resource, error) {
var result []resource.Resource
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + kind.ListPath(parentPathValue)
requestBuilder := client.client.R()
if queryParams != nil {
Expand Down Expand Up @@ -243,7 +316,7 @@ func (client *Client) Login(username, password string) (LoginResult, error) {

func (client *Client) Describe(kind *schema.Kind, parentPathValue []string, name string) (resource.Resource, error) {
var result resource.Resource
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + kind.DescribePath(parentPathValue, name)
resp, err := client.client.R().Get(url)
if err != nil {
Expand All @@ -256,7 +329,7 @@ func (client *Client) Describe(kind *schema.Kind, parentPathValue []string, name
}

func (client *Client) Delete(kind *schema.Kind, parentPathValue []string, name string) error {
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + kind.DescribePath(parentPathValue, name)
resp, err := client.client.R().Delete(url)
if err != nil {
Expand All @@ -271,7 +344,7 @@ func (client *Client) Delete(kind *schema.Kind, parentPathValue []string, name s
}

func (client *Client) DeleteResource(resource *resource.Resource) error {
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
kinds := client.GetKinds()
kind, ok := kinds[resource.Kind]
if !ok {
Expand Down Expand Up @@ -324,7 +397,7 @@ func (client *Client) initKindFromApi() error {

func (client *Client) ListAdminToken() ([]Token, error) {
result := make([]Token, 0)
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/token/v1/admin_tokens"
resp, err := client.client.R().Get(url)
if err != nil {
Expand All @@ -339,7 +412,7 @@ func (client *Client) ListAdminToken() ([]Token, error) {

func (client *Client) ListApplicationInstanceToken(applicationInstanceName string) ([]Token, error) {
result := make([]Token, 0)
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/token/v1/application_instance_tokens/" + applicationInstanceName
resp, err := client.client.R().Get(url)
if err != nil {
Expand All @@ -354,7 +427,7 @@ func (client *Client) ListApplicationInstanceToken(applicationInstanceName strin

func (client *Client) CreateAdminToken(name string) (CreatedToken, error) {
var result CreatedToken
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/token/v1/admin_tokens"
resp, err := client.client.R().SetBody(map[string]string{"name": name}).Post(url)
if err != nil {
Expand All @@ -369,7 +442,7 @@ func (client *Client) CreateAdminToken(name string) (CreatedToken, error) {

func (client *Client) CreateApplicationInstanceToken(applicationInstanceName, name string) (CreatedToken, error) {
var result CreatedToken
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/token/v1/application_instance_tokens/" + applicationInstanceName
resp, err := client.client.R().SetBody(map[string]string{"name": name}).Post(url)
if err != nil {
Expand All @@ -389,7 +462,7 @@ type SqlResult struct {

func (client *Client) ExecuteSql(maxLine int, sql string) (SqlResult, error) {
var result SqlResult
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/public/sql/v1/execute"
resp, err := client.client.R().SetBody(sql).SetQueryParam("maxLine", fmt.Sprintf("%d", maxLine)).Post(url)
if err != nil {
Expand All @@ -403,7 +476,7 @@ func (client *Client) ExecuteSql(maxLine int, sql string) (SqlResult, error) {
}

func (client *Client) DeleteToken(uuid string) error {
client.setApiKeyFromEnvIfNeeded()
client.setAuthMethodFromEnvIfNeeded()
url := client.baseUrl + "/token/v1/" + uuid
resp, err := client.client.R().Delete(url)
if err != nil {
Expand Down

0 comments on commit a059e92

Please sign in to comment.