Skip to content

Commit

Permalink
feat: kubernetes aws connection
Browse files Browse the repository at this point in the history
  • Loading branch information
adityathebe committed Nov 28, 2024
1 parent d61361f commit c13ac65
Show file tree
Hide file tree
Showing 10 changed files with 430 additions and 82 deletions.
23 changes: 12 additions & 11 deletions api/v1/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,18 @@ type KubernetesResourceToWatch struct {
}

type Kubernetes struct {
BaseScraper `json:",inline"`
ClusterName string `json:"clusterName"`
Namespace string `json:"namespace,omitempty"`
UseCache bool `json:"useCache,omitempty"`
AllowIncomplete bool `json:"allowIncomplete,omitempty"`
Scope string `json:"scope,omitempty"`
Since string `json:"since,omitempty"`
Selector string `json:"selector,omitempty"`
FieldSelector string `json:"fieldSelector,omitempty"`
MaxInflight int64 `json:"maxInflight,omitempty"`
Kubeconfig *types.EnvVar `json:"kubeconfig,omitempty"`
BaseScraper `json:",inline"`
KubernetesConnection `json:",inline"`

ClusterName string `json:"clusterName"`
Namespace string `json:"namespace,omitempty"`
UseCache bool `json:"useCache,omitempty"`
AllowIncomplete bool `json:"allowIncomplete,omitempty"`
Scope string `json:"scope,omitempty"`
Since string `json:"since,omitempty"`
Selector string `json:"selector,omitempty"`
FieldSelector string `json:"fieldSelector,omitempty"`
MaxInflight int64 `json:"maxInflight,omitempty"`

// Watch specifies which Kubernetes resources should be watched.
// This allows for near real-time updates of the config items
Expand Down
128 changes: 128 additions & 0 deletions api/v1/kubernetes_connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package v1

import (
gocontext "context"
"encoding/base64"
"fmt"
"net/http"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
signerv4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/service/eks"
dutyKubernetes "github.com/flanksource/duty/kubernetes"

"github.com/flanksource/duty/connection"
"github.com/flanksource/duty/context"
"github.com/flanksource/duty/types"
"k8s.io/client-go/kubernetes"
rest "k8s.io/client-go/rest"
)

const (
clusterIDHeader = "x-k8s-aws-id"
emptyStringSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
v1Prefix = "k8s-aws-v1."
)

type EKSConnection struct {
connection.AWSConnection `json:",inline" yaml:",inline"`

Cluster string `json:"cluster"`
}

type KubernetesConnection struct {
Kubeconfig *types.EnvVar `json:"kubeconfig,omitempty"`
EKS *EKSConnection `json:"eks,omitempty"`
}

func (t *KubernetesConnection) Populate(ctx context.Context) (kubernetes.Interface, *rest.Config, error) {
if t.Kubeconfig != nil {
return dutyKubernetes.NewClientFromPathOrConfig(ctx.Logger, t.Kubeconfig.ValueStatic)
}

if t.EKS != nil {
if err := t.EKS.Populate(ctx); err != nil {
return nil, nil, err
}

conf, err := t.EKS.Client(ctx)
if err != nil {
return nil, nil, err
}

eksEndpoint, ca, err := eksClusterDetails(ctx, t.EKS.Cluster, conf)
if err != nil {
return nil, nil, err
}

token, err := getToken(ctx, t.EKS.Cluster, conf)
if err != nil {
return nil, nil, fmt.Errorf("failed to get token for EKS: %w", err)
}

restConfig := &rest.Config{
Host: eksEndpoint,
BearerToken: token,
TLSClientConfig: rest.TLSClientConfig{
CAData: ca,
},
}

clientset, err := kubernetes.NewForConfig(restConfig)
if err != nil {
return nil, nil, err
}

return clientset, restConfig, nil
}

return nil, nil, nil
}

func eksClusterDetails(ctx gocontext.Context, clusterName string, conf aws.Config) (string, []byte, error) {
eksClient := eks.NewFromConfig(conf)
cluster, err := eksClient.DescribeCluster(ctx, &eks.DescribeClusterInput{Name: &clusterName})
if err != nil {
return "", nil, fmt.Errorf("unable to get cluster info: %v", err)
}

ca, err := base64.URLEncoding.DecodeString(*cluster.Cluster.CertificateAuthority.Data)
if err != nil {
return "", nil, fmt.Errorf("unable to presign URL: %v", err)
}

return *cluster.Cluster.Endpoint, ca, nil
}

func getToken(ctx gocontext.Context, cluster string, conf aws.Config) (string, error) {
cred, err := conf.Credentials.Retrieve(ctx)
if err != nil {
return "", fmt.Errorf("failed to retrive credentials from aws config: %w", err)
}

signedURI, err := getSignedURI(ctx, cluster, cred)
if err != nil {
return "", fmt.Errorf("failed to get signed URI: %w", err)
}

token := v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(signedURI))
return token, nil
}

func getSignedURI(ctx gocontext.Context, cluster string, cred aws.Credentials) (string, error) {
request, err := http.NewRequest(http.MethodGet, "https://sts.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15", nil)
if err != nil {
return "", err
}

request.Header.Add(clusterIDHeader, cluster)
request.Header.Add("X-Amz-Expires", "0")
signer := signerv4.NewSigner()
signedURI, _, err := signer.PresignHTTP(ctx, cred, request, emptyStringSha256, "sts", "us-east-1", time.Now())
if err != nil {
return "", err
}

return signedURI, nil
}
47 changes: 42 additions & 5 deletions api/v1/zz_generated.deepcopy.go

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

149 changes: 149 additions & 0 deletions chart/crds/configs.flanksource.com_scrapeconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3473,6 +3473,155 @@ spec:
description: A static value or JSONPath expression to use as
the description for the resource.
type: string
eks:
properties:
accessKey:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used
to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service
account whose token should be fetched
type: string
type: object
type: object
assumeRole:
type: string
cluster:
type: string
connection:
description: ConnectionName of the connection. It'll be
used to populate the endpoint, accessKey and secretKey.
type: string
endpoint:
type: string
region:
type: string
secretKey:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used
to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service
account whose token should be fetched
type: string
type: object
type: object
sessionToken:
properties:
name:
type: string
value:
type: string
valueFrom:
properties:
configMapKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
helmRef:
properties:
key:
description: Key is a JSONPath expression used
to fetch the key from the merged JSON.
type: string
name:
type: string
required:
- key
type: object
secretKeyRef:
properties:
key:
type: string
name:
type: string
required:
- key
type: object
serviceAccount:
description: ServiceAccount specifies the service
account whose token should be fetched
type: string
type: object
type: object
skipTLSVerify:
description: Skip TLS verify when connecting to aws
type: boolean
required:
- cluster
type: object
event:
description: Event specifies how the Kubernetes event should
be handled.
Expand Down
Loading

0 comments on commit c13ac65

Please sign in to comment.