Skip to content

Commit

Permalink
terraform-provider: implement constellation_cluster resource (#2691)
Browse files Browse the repository at this point in the history
* terraform: move module to legacy-directory

* constellation-lib: refactor service account marshalling

* terraform-provider: normalize Azure image URIs

* constellation-lib: refactor Kubeconfig endpoint rewriting

* terraform-provider: add conversion functions for AWS and GCP

* terraform-provider: implement `constellation_cluster` resource

* terraform-provider: refactor conversion

* terraform-provider: implement image and k8s upgrades

* terraform-provider: fix linter checks

* terraform-provider: refactor to bundle init & upgrade method

* constellation-lib: rewrite Kubeconfig endpoint in init

* terraform-provider: bind logger and dialer constructors to struct

* terraform-provider: move applier to function pointer

* terraform-provider: gcp conversion fixes

Signed-off-by: Moritz Sanft <[email protected]>

* terraform-provider: fix Azure UAMI input

* terraform-provider: rename Kubeconfig variable

* terraform-provider: tidy

* terraform-provider: regenerate docs

* constellation-lib: provide Kubeconfig in testing initserver

---------

Signed-off-by: Moritz Sanft <[email protected]>
  • Loading branch information
msanft authored Dec 11, 2023
1 parent 767bac4 commit 60fc73e
Show file tree
Hide file tree
Showing 40 changed files with 1,469 additions and 323 deletions.
8 changes: 4 additions & 4 deletions .github/actions/upload_terraform_module/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ runs:
shell: bash
run: |
sed -i "s/@@CONSTELLATION_VERSION@@/${{ inputs.version }}/g" \
terraform-module/constellation-cluster/variables.tf \
terraform-module/aws-constellation/variables.tf \
terraform-module/azure-constellation/variables.tf \
terraform-module/gcp-constellation/variables.tf
terraform-module/legacy-module/constellation-cluster/variables.tf \
terraform-module/legacy-module/aws-constellation/variables.tf \
terraform-module/legacy-module/azure-constellation/variables.tf \
terraform-module/legacy-module/gcp-constellation/variables.tf
- name: Zip terraform dir
shell: bash
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/e2e-test-tf-module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
- name: Create AWS Terraform variable input file
if: inputs.cloudProvider == 'aws'
working-directory: ${{ github.workspace }}/terraform-module/aws-constellation
working-directory: ${{ github.workspace }}/terraform-module/legacy-module/aws-constellation
shell: bash
run: |
cat > terraform.tfvars <<EOF
Expand Down Expand Up @@ -123,7 +123,7 @@ jobs:
- name: Create Azure Terraform variable input file
if: inputs.cloudProvider == 'azure'
working-directory: ${{ github.workspace }}/terraform-module/azure-constellation
working-directory: ${{ github.workspace }}/terraform-module/legacy-module/azure-constellation
shell: bash
run: |
cat > terraform.tfvars <<EOF
Expand Down Expand Up @@ -154,7 +154,7 @@ jobs:
- name: Create GCP Terraform variable input file
if: inputs.cloudProvider == 'gcp'
working-directory: ${{ github.workspace }}/terraform-module/gcp-constellation
working-directory: ${{ github.workspace }}/terraform-module/legacy-module/gcp-constellation
shell: bash
run: |
cat > terraform.tfvars <<EOF
Expand Down
1 change: 1 addition & 0 deletions cli/internal/cloudcmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ go_library(
"//internal/cloud/openstack",
"//internal/config",
"//internal/constants",
"//internal/constellation",
"//internal/constellation/state",
"//internal/file",
"//internal/imagefetcher",
Expand Down
23 changes: 7 additions & 16 deletions cli/internal/cloudcmd/serviceaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,31 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/cloud/openstack"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constellation"
"github.com/edgelesssys/constellation/v2/internal/file"
)

// GetMarshaledServiceAccountURI returns the service account URI for the given cloud provider.
func GetMarshaledServiceAccountURI(config *config.Config, fileHandler file.Handler) (string, error) {
payload := constellation.ServiceAccountPayload{}
switch config.GetProvider() {
case cloudprovider.GCP:
var key gcpshared.ServiceAccountKey
if err := fileHandler.ReadJSON(config.Provider.GCP.ServiceAccountKeyPath, &key); err != nil {
return "", fmt.Errorf("reading service account key: %w", err)
}
return key.ToCloudServiceAccountURI(), nil

case cloudprovider.AWS:
return "", nil // AWS does not need a service account URI
payload.GCP = key

case cloudprovider.Azure:
authMethod := azureshared.AuthMethodUserAssignedIdentity

creds := azureshared.ApplicationCredentials{
payload.Azure = azureshared.ApplicationCredentials{
TenantID: config.Provider.Azure.TenantID,
Location: config.Provider.Azure.Location,
PreferredAuthMethod: authMethod,
PreferredAuthMethod: azureshared.AuthMethodUserAssignedIdentity,
UamiResourceID: config.Provider.Azure.UserAssignedIdentity,
}
return creds.ToCloudServiceAccountURI(), nil

case cloudprovider.OpenStack:
creds := openstack.AccountKey{
payload.OpenStack = openstack.AccountKey{
AuthURL: config.Provider.OpenStack.AuthURL,
Username: config.Provider.OpenStack.Username,
Password: config.Provider.OpenStack.Password,
Expand All @@ -52,12 +48,7 @@ func GetMarshaledServiceAccountURI(config *config.Config, fileHandler file.Handl
ProjectDomainName: config.Provider.OpenStack.ProjectDomainName,
RegionName: config.Provider.OpenStack.RegionName,
}
return creds.ToCloudServiceAccountURI(), nil

case cloudprovider.QEMU:
return "", nil // QEMU does not use service account keys

default:
return "", fmt.Errorf("unsupported cloud provider %q", config.GetProvider())
}
return constellation.MarshalServiceAccountURI(config.GetProvider(), payload)
}
1 change: 0 additions & 1 deletion cli/internal/cmd/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/cli/internal/cmd",
visibility = ["//cli:__subpackages__"],
deps = [
"//bootstrapper/initproto",
"//cli/internal/cloudcmd",
"//cli/internal/cmd/pathprefix",
"//cli/internal/libvirt",
Expand Down
3 changes: 1 addition & 2 deletions cli/internal/cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"strings"
"time"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
Expand Down Expand Up @@ -823,7 +822,7 @@ type applier interface {
Init(
ctx context.Context, validator atls.Validator, state *state.State,
clusterLogWriter io.Writer, payload constellation.InitPayload,
) (*initproto.InitSuccessResponse, error)
) (constellation.InitOutput, error)

// methods required to install/upgrade Helm charts

Expand Down
7 changes: 3 additions & 4 deletions cli/internal/cmd/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"testing"
"time"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
Expand Down Expand Up @@ -502,7 +501,7 @@ type stubConstellApplier struct {
generateMasterSecretErr error
generateMeasurementSaltErr error
initErr error
initResponse *initproto.InitSuccessResponse
initOutput constellation.InitOutput
*stubKubernetesUpgrader
helmApplier
}
Expand All @@ -521,8 +520,8 @@ func (s *stubConstellApplier) GenerateMeasurementSalt() ([]byte, error) {
return s.measurementSalt, s.generateMeasurementSaltErr
}

func (s *stubConstellApplier) Init(context.Context, atls.Validator, *state.State, io.Writer, constellation.InitPayload) (*initproto.InitSuccessResponse, error) {
return s.initResponse, s.initErr
func (s *stubConstellApplier) Init(context.Context, atls.Validator, *state.State, io.Writer, constellation.InitPayload) (constellation.InitOutput, error) {
return s.initOutput, s.initErr
}

type helmApplier interface {
Expand Down
39 changes: 5 additions & 34 deletions cli/internal/cmd/applyinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ package cmd

import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"io"
"net"
"net/url"
"path/filepath"
"text/tabwriter"

"github.com/edgelesssys/constellation/v2/bootstrapper/initproto"
"github.com/edgelesssys/constellation/v2/internal/attestation/choose"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
Expand All @@ -26,7 +22,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/kms/uri"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)

// runInit runs the init RPC to set up the Kubernetes cluster.
Expand Down Expand Up @@ -106,48 +101,24 @@ func (a *applyCmd) generateAndPersistMasterSecret(outWriter io.Writer) (uri.Mast
// writeInitOutput writes the output of a cluster initialization to the
// state- / kubeconfig-file and saves it to disk.
func (a *applyCmd) writeInitOutput(
stateFile *state.State, initResp *initproto.InitSuccessResponse,
stateFile *state.State, initResp constellation.InitOutput,
mergeConfig bool, wr io.Writer, measurementSalt []byte,
) error {
fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n")

ownerID := hex.EncodeToString(initResp.GetOwnerId())
clusterID := hex.EncodeToString(initResp.GetClusterId())

stateFile.SetClusterValues(state.ClusterValues{
MeasurementSalt: measurementSalt,
OwnerID: ownerID,
ClusterID: clusterID,
OwnerID: initResp.OwnerID,
ClusterID: initResp.ClusterID,
})

tw := tabwriter.NewWriter(wr, 0, 0, 2, ' ', 0)
writeRow(tw, "Constellation cluster identifier", clusterID)
writeRow(tw, "Constellation cluster identifier", initResp.ClusterID)
writeRow(tw, "Kubernetes configuration", a.flags.pathPrefixer.PrefixPrintablePath(constants.AdminConfFilename))
tw.Flush()
fmt.Fprintln(wr)

a.log.Debugf("Rewriting cluster server address in kubeconfig to %s", stateFile.Infrastructure.ClusterEndpoint)
kubeconfig, err := clientcmd.Load(initResp.GetKubeconfig())
if err != nil {
return fmt.Errorf("loading kubeconfig: %w", err)
}
if len(kubeconfig.Clusters) != 1 {
return fmt.Errorf("expected exactly one cluster in kubeconfig, got %d", len(kubeconfig.Clusters))
}
for _, cluster := range kubeconfig.Clusters {
kubeEndpoint, err := url.Parse(cluster.Server)
if err != nil {
return fmt.Errorf("parsing kubeconfig server URL: %w", err)
}
kubeEndpoint.Host = net.JoinHostPort(stateFile.Infrastructure.ClusterEndpoint, kubeEndpoint.Port())
cluster.Server = kubeEndpoint.String()
}
kubeconfigBytes, err := clientcmd.Write(*kubeconfig)
if err != nil {
return fmt.Errorf("marshaling kubeconfig: %w", err)
}

if err := a.fileHandler.Write(constants.AdminConfFilename, kubeconfigBytes, file.OptNone); err != nil {
if err := a.fileHandler.Write(constants.AdminConfFilename, initResp.Kubeconfig, file.OptNone); err != nil {
return fmt.Errorf("writing kubeconfig: %w", err)
}
a.log.Debugf("Kubeconfig written to %s", a.flags.pathPrefixer.PrefixPrintablePath(constants.AdminConfFilename))
Expand Down
Loading

0 comments on commit 60fc73e

Please sign in to comment.