Skip to content

Commit

Permalink
Merge pull request #10 from ekristen/quickstart
Browse files Browse the repository at this point in the history
feat: automatic cluster register
  • Loading branch information
ekristen authored Dec 7, 2023
2 parents fd57593 + 6bbe090 commit 10e38dc
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.6-labs

FROM debian:bullseye-slim as base
FROM debian:bookworm-slim as base
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
RUN useradd -r -u 999 -d /home/fides fides

Expand Down
4 changes: 2 additions & 2 deletions pkg/commands/controllers/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Execute(c *cli.Context) error {

// TODO: check for cluster values

cfg := oidc.Config{
cfg := &oidc.Config{
KubeConfigPath: c.String("kubeconfig"),
Namespace: c.String("namespace"),
Lockname: c.String("lockname"),
Expand Down Expand Up @@ -73,7 +73,7 @@ func init() {
},
&cli.StringFlag{
Name: "cluster-id",
EnvVars: []string{"FIDES_CLUSTER_ID"},
EnvVars: []string{"FIDES_CLUSTER_ID", "FIDES_CLUSTER_UID"},
},
}

Expand Down
204 changes: 181 additions & 23 deletions pkg/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/rancher/wrangler/v2/pkg/kubeconfig"
"github.com/rancher/wrangler/v2/pkg/leader"
"github.com/sirupsen/logrus"
"github.com/ekristen/fides/pkg/common"
"io"
"io/ioutil"
"net/http"
"time"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"net/http"
"time"

"github.com/rancher/wrangler/v2/pkg/kubeconfig"
"github.com/rancher/wrangler/v2/pkg/leader"
"github.com/sirupsen/logrus"

"github.com/ekristen/fides/pkg/types"
)

var UserAgent = fmt.Sprintf("fides/%s", common.AppVersion.Summary)

type Config struct {
KubeConfigPath string
Namespace string
Expand All @@ -30,7 +36,7 @@ type Config struct {
ClusterID string
}

func NewController(ctx context.Context, config Config) error {
func NewController(ctx context.Context, config *Config) error {
cfg, err := kubeconfig.GetNonInteractiveClientConfig(config.KubeConfigPath).ClientConfig()
if err != nil {
return err
Expand All @@ -57,25 +63,27 @@ func NewController(ctx context.Context, config Config) error {
return nil
}

func sync(ctx context.Context, kube *kubernetes.Clientset, config Config) error {
func sync(ctx context.Context, kube *kubernetes.Clientset, config *Config) error {
logrus.Info("sync called")

ks, err := kube.CoreV1().Namespaces().Get(ctx, "kube-system", v1.GetOptions{})
if err != nil {
return err
}

kubeUID := ks.GetUID()

firstTicker := time.NewTicker(1 * time.Second)
ticker := time.NewTicker(30 * time.Second)
ticker := time.NewTicker(15 * time.Minute)
for {
select {
case <-firstTicker.C:
if err := doSync(ctx, kube, config, ks.GetUID()); err != nil {
if err := doSync(ctx, kube, config, kubeUID); err != nil {
logrus.WithError(err).Error("unable to perform sync")
}
firstTicker.Stop()
case <-ticker.C:
if err := doSync(ctx, kube, config, ks.GetUID()); err != nil {
if err := doSync(ctx, kube, config, kubeUID); err != nil {
logrus.WithError(err).Error("unable to perform sync")
}
case <-ctx.Done():
Expand All @@ -84,20 +92,58 @@ func sync(ctx context.Context, kube *kubernetes.Clientset, config Config) error
}
}

func doSync(ctx context.Context, kube *kubernetes.Clientset, config Config, uid apitypes.UID) error {
func doSync(ctx context.Context, kube *kubernetes.Clientset, config *Config, kubeUID apitypes.UID) error {
logrus.Info("running doSync")

// 1. check the secret for cluster-id/cluster-key
// 2. if it does not exist, register the cluster
// 3. else update the cluster
newCluster := false
secret, err := kube.CoreV1().Secrets(config.Namespace).Get(ctx, config.SecretName, v1.GetOptions{})
if err != nil {
if !apierrors.IsNotFound(err) {
return err
}
}

clusterKey := string(secret.Data["cluster-key"])
clusterID := string(secret.Data["cluster-uid"])
clusterName := string(secret.Data["cluster-name"])

if clusterKey == "quickstart" {
newCluster = true
}

logrus.Infof("is cluster new: %t", newCluster)

if !newCluster {
if config.ClusterKey == "" || config.ClusterKey != clusterKey {
config.ClusterKey = clusterKey
}
if config.ClusterID == "" || config.ClusterID != clusterID {
config.ClusterID = clusterID
}
if config.ClusterName == "" || config.ClusterName != clusterName {
config.ClusterName = clusterName
}
}

logrus.WithFields(logrus.Fields{
"cluster-uid": config.ClusterID,
"cluster-name": config.ClusterName,
}).Info("cluster information")

resConfig := kube.RESTClient().Get().AbsPath("/.well-known/openid-configuration").Do(ctx)
configData, err := resConfig.Raw()
if err != nil {
logrus.WithError(err).Fatal("unable to retrieve raw data")
logrus.WithError(err).Fatal("unable to retrieve openid configuration")
return err
}

resJWKs := kube.RESTClient().Get().AbsPath("/openid/v1/jwks").Do(ctx)
jwkData, err := resJWKs.Raw()
if err != nil {
logrus.WithError(err).Fatal("unable to retrieve raw data")
logrus.WithError(err).Fatal("unable to retrieve jwks")
return err
}

Expand All @@ -111,8 +157,25 @@ func doSync(ctx context.Context, kube *kubernetes.Clientset, config Config, uid
return err
}

if newCluster {
// register the cluster
if err := registerCluster(ctx, kube, config, kubeUID, wellKnown, jwks); err != nil {
return err
}
}

// update the cluster
return updateCluster(ctx, kube, config, kubeUID, wellKnown, jwks)
}

func updateCluster(ctx context.Context, kube *kubernetes.Clientset, config *Config, kubeUID apitypes.UID, wellKnown types.OpenIDConfiguration, jwks types.JWKS) error {
logrus.Info("updating cluster")

ctx, cancel := context.WithDeadlineCause(ctx, time.Now().Add(30*time.Second), fmt.Errorf("register cluster"))
defer cancel()

reg := types.ClusterPutRequest{
UID: string(uid),
KubeUID: string(kubeUID),
OIDConfig: wellKnown,
JWKS: jwks,
}
Expand All @@ -122,11 +185,13 @@ func doSync(ctx context.Context, kube *kubernetes.Clientset, config Config, uid
return err
}

req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/clusters/%s", config.BaseURL, config.ClusterID), b)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("%s/api/v1/clusters/%s", config.BaseURL, config.ClusterID), b)
if err != nil {
return err
}

req.Header.Add("User-Agent", UserAgent)

if config.ClusterKey != "" {
// existing cluster token to http request
req.Header.Add("x-cluster-key", config.ClusterKey)
Expand All @@ -147,22 +212,115 @@ func doSync(ctx context.Context, kube *kubernetes.Clientset, config Config, uid
}
}(res.Body)

if res.StatusCode == 200 {
data, err := io.ReadAll(res.Body)
if err != nil {
logrus.WithError(err).Error("unable to read body")
return err
}

switch res.StatusCode {
case 200:
logrus.Info("cluster updated successfully")
} else {
data, err := ioutil.ReadAll(res.Body)
var res types.ClusterPutResponse
if err := json.Unmarshal(data, &res); err != nil {
logrus.WithError(err).Error("unable to parse response")
return err
}

if res.Verified == false {
logrus.Warn("cluster is not verified")
logrus.Warn("to verify your cluster you need to update your cluster configuration")
logrus.Warn("please add the following arguments to your kube-apiserver")
logrus.Warn("--service-account-issuer=https://oidc.fides.ekristen.dev/c/%s", config.ClusterID)
logrus.Warn("--service-account-jwks-uri=https://oidc.fides.ekristen.dev/c/%s/jwks", config.ClusterID)
}
default:
logrus.WithField("data", string(data)).WithField("status", res.StatusCode).Error("unknown status code")
}

return nil
}

func registerCluster(ctx context.Context, kube *kubernetes.Clientset, config *Config, kubeUID apitypes.UID, wellKnown types.OpenIDConfiguration, jwks types.JWKS) error {
logrus.Info("registering cluster")

ctx, cancel := context.WithDeadlineCause(ctx, time.Now().Add(30*time.Second), fmt.Errorf("register cluster"))
defer cancel()

regInput := types.ClusterNewRequest{
Name: config.ClusterName,
UID: config.ClusterID,
KubeUID: string(kubeUID),
OIDConfig: wellKnown,
JWKS: jwks,
}
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(regInput); err != nil {
return err
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/api/v1/clusters", config.BaseURL), b)
if err != nil {
return err
}

req.Header.Add("User-Agent", UserAgent)
req.Header.Add("x-fides-quickstart", "true")

client := http.Client{
Timeout: 30 * time.Second,
}

res, err := client.Do(req)
if err != nil {
return err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
logrus.WithError(err).Error("unable to close body")
}
}(res.Body)

switch res.StatusCode {
case 201:
logrus.Info("cluster registered successfully")
d, err := io.ReadAll(res.Body)
if err != nil {
logrus.WithError(err).Error("unable to read body")
return err
}

var resp types.Response
if err := json.Unmarshal(data, &resp); err != nil {
var resp types.ClusterNewResponse
if err := json.Unmarshal(d, &resp); err != nil {
logrus.WithError(err).Error("unable to parse response")
return err
}

logrus.WithError(fmt.Errorf(resp.Error)).Error("an error occurred updating the cluster information")
secret, err := kube.CoreV1().Secrets(config.Namespace).Update(ctx, &corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: config.SecretName,
Namespace: config.Namespace,
},
StringData: map[string]string{
"cluster-uid": resp.UID,
"cluster-key": resp.Token,
"cluster-name": resp.Name,
},
}, v1.UpdateOptions{})
if err != nil {
return err
}
_ = secret
case 409:
logrus.Error("cluster already exists")
return fmt.Errorf("cluster already exists")
default:
data, err := io.ReadAll(res.Body)
if err != nil {
logrus.WithError(err).Error("unable to read body")
return err
}
logrus.WithField("data", string(data)).WithField("status", res.StatusCode).Error("unknown status code")
}

return nil
Expand Down
21 changes: 21 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ package types
type ClusterPutRequest struct {
ID string `path:"id"`
UID string `json:"uid"`
KubeUID string `json:"kube_uid"`
JWKS JWKS `json:"jwks"`
OIDConfig OpenIDConfiguration `json:"oid_config"`
}

type ClusterPutResponse struct {
Error string `json:"error,omitempty"`
Verified bool `json:"verified,omitempty"`
}

type OpenIDConfiguration struct {
Issuer string `json:"issuer"`
JwksUri string `json:"jwks_uri"`
Expand Down Expand Up @@ -47,3 +53,18 @@ type Response struct {

Metadata *MetadataResponse `json:"metadata,omitempty"`
}

type ClusterNewRequest struct {
Name string `json:"name"`
UID string `json:"uid"`
KubeUID string `json:"kube_uid"`
JWKS JWKS `json:"jwks"`
OIDConfig OpenIDConfiguration `json:"oid_config"`
}

type ClusterNewResponse struct {
Token string `json:"token"`
UID string `json:"uid"`
URL string `json:"url"`
Name string `json:"name"`
}

0 comments on commit 10e38dc

Please sign in to comment.