Skip to content

Commit

Permalink
Support new Nutanix-style credentials in "secretdir" (#34)
Browse files Browse the repository at this point in the history
* Refactored Prism credential types and parsing into own package

* Addressed PR feedback

* Generated DeepCopy function to satisfy k8s Object interface

```
controller-gen object paths=./environment/credentials/types.go
```

* Made namespace optional in NutanixCredentialReference

* Add kubebuilder validations for credential types (#35)

Co-authored-by: Yannick Struyf <[email protected]>
  • Loading branch information
heiko-koehler and yannickstruyf3 authored Sep 27, 2022
1 parent 4b76a34 commit 99bc92c
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `WithCertificate` functional option for v3 client constructor
- Add `WithRoundTripper` functional option for v3 client to add custom interceptors
- Add `WithLogger` functional option for v3 client
- Add support for Nutanix-style credentials to "secretdir" environment provider

### Changed
- The http client has been moved from pkg/nutanix to repo root
Expand Down
37 changes: 37 additions & 0 deletions environment/credentials/parsecreds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package credentials

import (
"encoding/json"
"fmt"

"github.com/nutanix-cloud-native/prism-go-client/environment/types"
)

func ParseCredentials(credsData []byte) (*types.ApiCredentials, error) {
creds := &NutanixCredentials{}
err := json.Unmarshal(credsData, &creds.Credentials)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the credentials data. %w", err)
}
// TODO only single API endpoint supported
for _, cred := range creds.Credentials {
switch cred.Type {
case BasicAuthCredentialType:
basicAuthCreds := BasicAuthCredential{}
if err := json.Unmarshal(cred.Data, &basicAuthCreds); err != nil {
return nil, fmt.Errorf("failed to unmarshal the basic-auth data. %w", err)
}
pc := basicAuthCreds.PrismCentral
if pc.Username == "" || pc.Password == "" {
return nil, fmt.Errorf("the PrismCentral credentials data is not set")
}
return &types.ApiCredentials{
Username: pc.Username,
Password: pc.Password,
}, nil
default:
return nil, fmt.Errorf("unsupported credentials type: %v", cred.Type)
}
}
return nil, fmt.Errorf("no Prism credentials")
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package kubernetes
package credentials

// This file defines single-key secret format by encoding entire secret
// as JSON object.

import (
"encoding/json"
Expand All @@ -10,15 +13,20 @@ type CredentialType string
const (
// BasicAuthCredentialType is username/password based authentication.
BasicAuthCredentialType CredentialType = "basic_auth"

// KeyName is secret
KeyName = "credentials"
)

// +kubebuilder:object:generate=true
type Credential struct {
Type CredentialType `json:"type"`
Data json.RawMessage `json:"data"`
}

// NutanixCredentials is list of credentials to be embedded in other objects like
// Kubernetes secrets.
// +kubebuilder:object:generate=true
type NutanixCredentials struct {
Credentials []Credential `json:"credentials"`
}
Expand All @@ -31,15 +39,18 @@ type BasicAuthCredential struct {
PrismElements []PrismElementBasicAuth `json:"prismElements"`
}

// +kubebuilder:object:generate=true
type BasicAuth struct {
Username string `json:"username"`
Password string `json:"password"`
}

// +kubebuilder:object:generate=true
type PrismCentralBasicAuth struct {
BasicAuth `json:",inline"`
}

// +kubebuilder:object:generate=true
type PrismElementBasicAuth struct {
BasicAuth `json:",inline"`
// Name is the unique resource name of the Prism Element (cluster) in the Prism Central's domain
Expand All @@ -53,23 +64,36 @@ const (
SecretKind = NutanixCredentialKind("Secret")
)

// +kubebuilder:object:generate=true
type NutanixCredentialReference struct {
// Kind of the Nutanix credential
// +kubebuilder:validation:Enum=Secret
Kind NutanixCredentialKind `json:"kind"`
// Name of the credential.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
// namespace of the credential.
// +optional
Namespace string `json:"namespace"`
}

// NutanixPrismEndpoint defines a Nutanix API endpoint with reference to credentials.
// Credentials are stored in Kubernetes secrets.
// +kubebuilder:object:generate=true
type NutanixPrismEndpoint struct {
// address is the endpoint address (DNS name or IP address) of the Nutanix Prism Central or Element (cluster)
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=256
Address string `json:"address"`
// port is the port number to access the Nutanix Prism Central or Element (cluster)
// +kubebuilder:validation:Required
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=65535
// +kubebuilder:default=9440
Port int32 `json:"port"`
// use insecure connection to Prism endpoint
// +kubebuilder:default=false
// +optional
Insecure bool `json:"insecure"`
// Pass credential information for the target Prism instance
Expand Down
134 changes: 134 additions & 0 deletions environment/credentials/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 9 additions & 35 deletions environment/providers/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
package kubernetes

import (
"encoding/json"
"fmt"
"net/url"

"github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
"github.com/nutanix-cloud-native/prism-go-client/environment/types"
coreinformers "k8s.io/client-go/informers/core/v1"
)

type provider struct {
secretInformer coreinformers.SecretInformer
prismEndpoint NutanixPrismEndpoint
prismEndpoint credentials.NutanixPrismEndpoint
}

func (prov *provider) getCredentials(_ types.Topology) (*types.ApiCredentials, error) {
Expand All @@ -26,42 +26,16 @@ func (prov *provider) getCredentials(_ types.Topology) (*types.ApiCredentials, e
}

// Parse credentials in secret
credsData, ok := secret.Data["credentials"]
credsData, ok := secret.Data[credentials.KeyName]
if !ok {
return nil, fmt.Errorf("no \"credentials\" data found in secret %s/%s",
ref.Namespace, ref.Name)
return nil, fmt.Errorf("no %q data found in secret %s/%s",
credentials.KeyName, ref.Namespace, ref.Name)
}
creds := &NutanixCredentials{}
err = json.Unmarshal(credsData, &creds.Credentials)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal the credentials data. %w", err)
}
// TODO only single API endpoint supported
for _, cred := range creds.Credentials {
switch cred.Type {
case BasicAuthCredentialType:
basicAuthCreds := BasicAuthCredential{}
if err := json.Unmarshal(cred.Data, &basicAuthCreds); err != nil {
return nil, fmt.Errorf("failed to unmarshal the basic-auth data. %w", err)
}
pc := basicAuthCreds.PrismCentral
if pc.Username == "" || pc.Password == "" {
return nil, fmt.Errorf("the PrismCentral credentials data is not set for secret %s/%s",
ref.Namespace, ref.Name)
}
return &types.ApiCredentials{
Username: pc.Username,
Password: pc.Password,
}, nil
default:
return nil, fmt.Errorf("unsupported credentials type in secret %s/%s: %v",
ref.Namespace, ref.Name, cred.Type)
}
}
return nil, fmt.Errorf("no Prism credentials in secret %s/%s", ref.Namespace, ref.Name)

return credentials.ParseCredentials(credsData)
}

// GetManagementEndpoint retrieves managment endpoint
// GetManagementEndpoint retrieves management endpoint
func (prov *provider) GetManagementEndpoint(
topology types.Topology,
) (*types.ManagementEndpoint, error) {
Expand Down Expand Up @@ -96,7 +70,7 @@ func (prov *provider) Get(topology types.Topology, key string) (
// Prism endpoint and a secretes informer as input.
// It's assumed secrets informer is already running and its cache has been synced.
func NewProvider(
prismEndpoint NutanixPrismEndpoint,
prismEndpoint credentials.NutanixPrismEndpoint,
secretInformer coreinformers.SecretInformer,
) types.Provider {
return &provider{
Expand Down
7 changes: 4 additions & 3 deletions environment/providers/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"

"github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
"github.com/nutanix-cloud-native/prism-go-client/environment/types"
)

Expand Down Expand Up @@ -67,12 +68,12 @@ var _ = Describe("Kubernetes Environment Provider", Ordered, func() {
`, username, password)),
},
})
prismEndpoint = NutanixPrismEndpoint{
prismEndpoint = credentials.NutanixPrismEndpoint{
Address: ip,
Port: 9440,
Insecure: true,
CredentialRef: &NutanixCredentialReference{
Kind: SecretKind,
CredentialRef: &credentials.NutanixCredentialReference{
Kind: credentials.SecretKind,
Name: secretName,
Namespace: secretNamespace,
},
Expand Down
Loading

0 comments on commit 99bc92c

Please sign in to comment.