Skip to content

Commit

Permalink
liqoctl: add identity command
Browse files Browse the repository at this point in the history
  • Loading branch information
aleoli committed Dec 27, 2023
1 parent ab6cc4e commit ee112fe
Show file tree
Hide file tree
Showing 20 changed files with 728 additions and 82 deletions.
1 change: 1 addition & 0 deletions cmd/liqoctl/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func newGenerateCommand(ctx context.Context, f *factory.Factory) *cobra.Command

options := &rest.GenerateOptions{
Factory: f,
Liqoctl: liqoctl,
}

for _, r := range liqoResources {
Expand Down
4 changes: 4 additions & 0 deletions cmd/liqoctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ import (
"github.com/liqotech/liqo/pkg/liqoctl/rest/configuration"
"github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayclient"
"github.com/liqotech/liqo/pkg/liqoctl/rest/gatewayserver"
"github.com/liqotech/liqo/pkg/liqoctl/rest/identity"
"github.com/liqotech/liqo/pkg/liqoctl/rest/publickey"
"github.com/liqotech/liqo/pkg/liqoctl/rest/virtualnode"
"github.com/liqotech/liqo/pkg/liqoctl/update"
)

var liqoctl string
Expand All @@ -50,6 +52,7 @@ var liqoResources = []rest.APIProvider{
gatewayserver.GatewayServer,
gatewayclient.GatewayClient,
publickey.PublicKey,
identity.Identity,
}

func init() {
Expand Down Expand Up @@ -138,6 +141,7 @@ func NewRootCommand(ctx context.Context) *cobra.Command {
cmd.AddCommand(get.NewGetCommand(ctx, liqoResources, f))
cmd.AddCommand(create.NewCreateCommand(ctx, liqoResources, f))
cmd.AddCommand(delete.NewDeleteCommand(ctx, liqoResources, f))
cmd.AddCommand(update.NewUpdateCommand(ctx, liqoResources, f))
return cmd
}

Expand Down
41 changes: 28 additions & 13 deletions pkg/identityManager/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"strconv"
"time"

v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
Expand All @@ -34,10 +34,14 @@ import (
"github.com/liqotech/liqo/pkg/discovery"
)

// StoreIdentity stores the identity to authenticate with a remote cluster.
func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteCluster discoveryv1alpha1.ClusterIdentity,
namespace string, key []byte, remoteProxyURL string, identityResponse *auth.CertificateIdentityResponse) error {
secret := &v1.Secret{
// GenerateIdentitySecret generates the identity secret to authenticate with a remote cluster.
func (certManager *identityManager) GenerateIdentitySecret(remoteCluster discoveryv1alpha1.ClusterIdentity,
namespace string, key []byte, remoteProxyURL string, identityResponse *auth.CertificateIdentityResponse) (*corev1.Secret, error) {
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: corev1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: identitySecretRoot + "-",
Namespace: namespace,
Expand Down Expand Up @@ -69,7 +73,7 @@ func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteClu
} else {
certificate, err := base64.StdEncoding.DecodeString(identityResponse.Certificate)
if err != nil {
return fmt.Errorf("failed to decode certificate: %w", err)
return nil, fmt.Errorf("failed to decode certificate: %w", err)
}

secret.Data[certificateSecretKey] = certificate
Expand All @@ -79,7 +83,7 @@ func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteClu
if identityResponse.APIServerCA != "" {
apiServerCa, err := base64.StdEncoding.DecodeString(identityResponse.APIServerCA)
if err != nil {
return fmt.Errorf("failed to decode certification authority: %w", err)
return nil, fmt.Errorf("failed to decode certification authority: %w", err)
}

secret.Data[apiServerCaSecretKey] = apiServerCa
Expand All @@ -89,14 +93,25 @@ func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteClu
secret.StringData[apiProxyURLSecretKey] = remoteProxyURL
}

if _, err := certManager.client.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
return secret, nil
}

// StoreIdentity generates and stores the identity to authenticate with a remote cluster.
func (certManager *identityManager) StoreIdentity(ctx context.Context, remoteCluster discoveryv1alpha1.ClusterIdentity,
namespace string, key []byte, remoteProxyURL string, identityResponse *auth.CertificateIdentityResponse) error {
secret, err := certManager.GenerateIdentitySecret(remoteCluster, namespace, key, remoteProxyURL, identityResponse)
if err != nil {
return err
}

if _, err = certManager.client.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create secret: %w", err)
}
return nil
}

// getSecret retrieves the identity secret given the clusterID.
func (certManager *identityManager) getSecret(remoteCluster discoveryv1alpha1.ClusterIdentity) (*v1.Secret, error) {
// GetSecret retrieves the identity secret given the clusterID.
func (certManager *identityManager) GetSecret(remoteCluster discoveryv1alpha1.ClusterIdentity) (*corev1.Secret, error) {
namespace, err := certManager.namespaceManager.GetNamespace(context.TODO(), remoteCluster)
if err != nil {
return nil, err
Expand All @@ -107,7 +122,7 @@ func (certManager *identityManager) getSecret(remoteCluster discoveryv1alpha1.Cl

// getSecretInNamespace retrieves the identity secret in the given Namespace.
func (certManager *identityManager) getSecretInNamespace(remoteCluster discoveryv1alpha1.ClusterIdentity,
namespace string) (*v1.Secret, error) {
namespace string) (*corev1.Secret, error) {
labelSelector := metav1.LabelSelector{
MatchLabels: map[string]string{
localIdentitySecretLabel: "true",
Expand Down Expand Up @@ -142,7 +157,7 @@ func (certManager *identityManager) getSecretInNamespace(remoteCluster discovery
}

// getExpireTime reads the expire time from the annotations of the secret.
func getExpireTime(secret *v1.Secret) int64 {
func getExpireTime(secret *corev1.Secret) int64 {
now := time.Now().Unix()
if secret.Annotations == nil {
klog.Warningf("annotation %v not found in secret %v/%v", certificateExpireTimeAnnotation, secret.Namespace, secret.Name)
Expand All @@ -163,7 +178,7 @@ func getExpireTime(secret *v1.Secret) int64 {
return n
}

func (certManager *identityManager) isAwsIdentity(secret *v1.Secret) bool {
func (certManager *identityManager) isAwsIdentity(secret *corev1.Secret) bool {
data := secret.Data
keys := []string{awsAccessKeyIDSecretKey, awsSecretAccessKeySecretKey, awsRegionSecretKey, awsEKSClusterIDSecretKey, awsIAMUserArnSecretKey}
for i := range keys {
Expand Down
17 changes: 14 additions & 3 deletions pkg/identityManager/certificateIdentityProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,21 @@ func (identityProvider *certificateIdentityProvider) storeRemoteCertificate(clus
},
}

if secret, err = identityProvider.client.CoreV1().
Secrets(namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
res, err := identityProvider.client.CoreV1().
Secrets(namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{})
switch {
case kerrors.IsAlreadyExists(err):
res, err = identityProvider.client.CoreV1().
Secrets(namespace.Name).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
klog.Error(err)
return nil, err
}
return res, nil
case err != nil:
klog.Error(err)
return nil, err
default:
return res, nil
}
return secret, nil
}
6 changes: 3 additions & 3 deletions pkg/identityManager/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (certManager *identityManager) GetConfig(remoteCluster discoveryv1alpha1.Cl
var err error

if namespace == "" {
secret, err = certManager.getSecret(remoteCluster)
secret, err = certManager.GetSecret(remoteCluster)
} else {
secret, err = certManager.getSecretInNamespace(remoteCluster, namespace)
}
Expand All @@ -57,7 +57,7 @@ func (certManager *identityManager) GetSecretNamespacedName(remoteCluster discov
var err error

if namespace == "" {
secret, err = certManager.getSecret(remoteCluster)
secret, err = certManager.GetSecret(remoteCluster)
} else {
secret, err = certManager.getSecretInNamespace(remoteCluster, namespace)
}
Expand Down Expand Up @@ -88,7 +88,7 @@ func (certManager *identityManager) GetRemoteTenantNamespace(remoteCluster disco
var err error

if localTenantNamespaceName == "" {
secret, err = certManager.getSecret(remoteCluster)
secret, err = certManager.GetSecret(remoteCluster)
} else {
secret, err = certManager.getSecretInNamespace(remoteCluster, localTenantNamespaceName)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/identityManager/fake/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,8 @@ func (i *IdentityReader) GetRemoteTenantNamespace(remoteCluster discoveryv1alpha
}
return "", fmt.Errorf("remote cluster ID %v not found", remoteCluster.ClusterID)
}

// GetSecret retrieves the secret associated with a remote cluster.
func (i *IdentityReader) GetSecret(_ discoveryv1alpha1.ClusterIdentity) (*corev1.Secret, error) {
panic("implement me")
}
4 changes: 2 additions & 2 deletions pkg/identityManager/identityManager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ var _ = Describe("IdentityManager", func() {
idMan, ok := identityMan.(*identityManager)
Expect(ok).To(BeTrue())

secret, err := idMan.getSecret(remoteCluster)
secret, err := idMan.GetSecret(remoteCluster)
Expect(err).To(Succeed())

commonSecretChecks(secret)
Expand Down Expand Up @@ -155,7 +155,7 @@ var _ = Describe("IdentityManager", func() {
}
idMan.iamTokenManager = &tokenManager

secret, err := idMan.getSecret(remoteCluster)
secret, err := idMan.GetSecret(remoteCluster)
Expect(err).To(Succeed())

commonSecretChecks(secret)
Expand Down
3 changes: 3 additions & 0 deletions pkg/identityManager/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type IdentityReader interface {
GetConfigFromSecret(secret *corev1.Secret) (*rest.Config, error)
GetRemoteTenantNamespace(remoteCluster discoveryv1alpha1.ClusterIdentity, namespace string) (string, error)
GetSecretNamespacedName(remoteCluster discoveryv1alpha1.ClusterIdentity, namespace string) (types.NamespacedName, error)
GetSecret(remoteCluster discoveryv1alpha1.ClusterIdentity) (*corev1.Secret, error)
}

// IdentityManager interface provides the methods to manage identities for the remote clusters.
Expand All @@ -40,6 +41,8 @@ type IdentityManager interface {

StoreIdentity(ctx context.Context, remoteCluster discoveryv1alpha1.ClusterIdentity, namespace string, key []byte,
remoteProxyURL string, identityResponse *auth.CertificateIdentityResponse) error
GenerateIdentitySecret(remoteCluster discoveryv1alpha1.ClusterIdentity,
namespace string, key []byte, remoteProxyURL string, identityResponse *auth.CertificateIdentityResponse) (*corev1.Secret, error)
}

// IdentityProvider provides the interface to retrieve and approve remote cluster identities.
Expand Down
63 changes: 2 additions & 61 deletions pkg/liqo-controller-manager/virtualnode-controller/drain.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ import (
"time"

corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"

virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1"
"github.com/liqotech/liqo/pkg/utils"
"github.com/liqotech/liqo/pkg/utils/indexer"
)

Expand All @@ -44,7 +41,7 @@ func drainNode(ctx context.Context, cl client.Client, vn *virtualkubeletv1alpha1
return err
}

if err = evictPods(ctx, cl, podsToEvict); err != nil {
if err = utils.EvictPods(ctx, cl, podsToEvict, waitForPodTerminationCheckPeriod); err != nil {
klog.Error(err)
return err
}
Expand All @@ -69,59 +66,3 @@ func getPodsForDeletion(ctx context.Context, cl client.Client, vn *virtualkubele
}
return podList, nil
}

// evictPods performs the eviction of the provided list of pods in parallel, waiting for their deletion.
func evictPods(ctx context.Context, cl client.Client, podList *corev1.PodList) error {
for i := range podList.Items {
if err := evictPod(ctx, cl, &podList.Items[i]); err != nil {
return err
}
}

for i := range podList.Items {
if err := waitPodForDelete(ctx, cl, &podList.Items[i]); err != nil {
return err
}
}

return nil
}

// evictPod evicts the provided pod and waits for its deletion.
func evictPod(ctx context.Context, cl client.Client, pod *corev1.Pod) error {
eviction := &policyv1.Eviction{
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
},
DeleteOptions: &metav1.DeleteOptions{},
}

if err := cl.SubResource("eviction").Create(ctx, pod, eviction); err != nil {
return err
}

klog.V(4).Infof("Drain node %s -> pod %v/%v eviction started", pod.Spec.NodeName, pod.Namespace, pod.Name)

return nil
}

// waitForDelete waits for the pod deletion.
func waitPodForDelete(ctx context.Context, cl client.Client, pod *corev1.Pod) error {
//nolint:staticcheck // Waiting for PollWithContextCancel implementation.
return wait.PollImmediateInfinite(waitForPodTerminationCheckPeriod, func() (bool, error) {
klog.Infof("Drain node %s -> pod %v/%v waiting for deletion", pod.Spec.NodeName, pod.Namespace, pod.Name)
updatedPod := &corev1.Pod{}
err := cl.Get(ctx, client.ObjectKey{Namespace: pod.Namespace, Name: pod.Name}, updatedPod)
if kerrors.IsNotFound(err) || (updatedPod != nil &&
pod.ObjectMeta.UID != updatedPod.ObjectMeta.UID) {
klog.Infof("Drain node %s -> pod %v/%v successfully deleted", pod.Spec.NodeName, pod.Namespace, pod.Name)
return true, nil
}
if err != nil {
klog.Error(err)
return false, err
}
return false, nil
})
}
54 changes: 54 additions & 0 deletions pkg/liqoctl/rest/identity/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2019-2023 The Liqo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package identity

import (
"context"
"fmt"
"os"

"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/printers"

"github.com/liqotech/liqo/pkg/liqoctl/rest"
)

// Create creates a VirtualNode.
func (o *Options) Create(_ context.Context, _ *rest.CreateOptions) *cobra.Command {
panic("not implemented")
}

// output implements the logic to output the generated Configuration resource.
func (o *Options) output(conf *corev1.Secret) error {
var outputFormat string
switch {
case o.generateOptions != nil:
outputFormat = o.generateOptions.OutputFormat
default:
return fmt.Errorf("unable to determine output format")
}
var printer printers.ResourcePrinter
switch outputFormat {
case yamlLabel:
printer = &printers.YAMLPrinter{}
case jsonLabel:
printer = &printers.JSONPrinter{}
default:
return fmt.Errorf("unsupported output format %q", outputFormat)
}

return printer.PrintObj(conf, os.Stdout)
}
Loading

0 comments on commit ee112fe

Please sign in to comment.