Skip to content

Commit

Permalink
feat: implement ephemeral partition encryption
Browse files Browse the repository at this point in the history
This PR introduces the first part of disk encryption support.
New config section `systemDiskEncryption` was added into MachineConfig.
For now it contains only Ephemeral partition encryption.

Encryption itself supports two kinds of keys for now:
- node id deterministic key.
- static key which is hardcoded in the config and mainly used for test
purposes.

Talosctl cluster create can now be told to encrypt ephemeral partition
by using `--encrypt-ephemeral` flag.

Additionally:
- updated pkgs library version.
- changed Dockefile to copy cryptsetup deps from pkgs.

Signed-off-by: Artem Chernyshev <artem.0xD2@gmail.com>
  • Loading branch information
Unix4ever authored and talos-bot committed Feb 17, 2021
1 parent e5bd35a commit 58ff2c9
Show file tree
Hide file tree
Showing 26 changed files with 1,340 additions and 129 deletions.
12 changes: 10 additions & 2 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -358,16 +358,24 @@ local integration_cilium = Step("e2e-cilium-1.8.5", target="e2e-qemu", privilege
"SHORT_INTEGRATION_TEST": "yes",
"CUSTOM_CNI_URL": "https://raw.githubusercontent.com/cilium/cilium/v1.8.5/install/kubernetes/quick-install.yaml",
"REGISTRY": local_registry,
"CLUSTER_CIDR": 2,
});
local integration_uefi = Step("e2e-uefi", target="e2e-qemu", privileged=true, depends_on=[integration_cilium], environment={
"SHORT_INTEGRATION_TEST": "yes",
"WITH_UEFI": "true",
"CLUSTER_CIDR": 3,
"REGISTRY": local_registry,
});
local integration_disk_image = Step("e2e-disk-image", target="e2e-qemu", privileged=true, depends_on=[integration_uefi], environment={
"SHORT_INTEGRATION_TEST": "yes",
"USE_DISK_IMAGE": "true",
"REGISTRY": local_registry,
"CLUSTER_CIDR": 4,
});
local integration_disk_encryption = Step("e2e-encrypted", target="e2e-qemu", privileged=true, depends_on=[integration_disk_image], environment={
"WITH_DISK_ENCRYPTION": "true",
"REGISTRY": local_registry,
"CLUSTER_CIDR": 5,
});

local push_edge = {
Expand Down Expand Up @@ -403,13 +411,13 @@ local integration_pipelines = [
Pipeline('integration-qemu', default_pipeline_steps + [integration_qemu, push_edge]) + integration_trigger(['integration-qemu']),
Pipeline('integration-provision-0', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_0]) + integration_trigger(['integration-provision', 'integration-provision-0']),
Pipeline('integration-provision-1', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_1]) + integration_trigger(['integration-provision', 'integration-provision-1']),
Pipeline('integration-misc', default_pipeline_steps + [integration_cilium, integration_uefi, integration_disk_image]) + integration_trigger(['integration-misc']),
Pipeline('integration-misc', default_pipeline_steps + [integration_cilium, integration_uefi, integration_disk_image, integration_disk_encryption]) + integration_trigger(['integration-misc']),

// cron pipelines, triggered on schedule events
Pipeline('cron-integration-qemu', default_pipeline_steps + [integration_qemu, push_edge]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-provision-0', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_0]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-provision-1', default_pipeline_steps + [integration_provision_tests_prepare, integration_provision_tests_track_1]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-misc', default_pipeline_steps + [integration_cilium, integration_uefi, integration_disk_image]) + cron_trigger(['thrice-daily', 'nightly']),
Pipeline('cron-integration-misc', default_pipeline_steps + [integration_cilium, integration_uefi, integration_disk_image, integration_disk_encryption]) + cron_trigger(['thrice-daily', 'nightly']),
];


Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ FROM ghcr.io/talos-systems/dosfstools:${PKGS} AS pkg-dosfstools
FROM ghcr.io/talos-systems/eudev:${PKGS} AS pkg-eudev
FROM ghcr.io/talos-systems/grub:${PKGS} AS pkg-grub
FROM ghcr.io/talos-systems/iptables:${PKGS} AS pkg-iptables
FROM ghcr.io/talos-systems/libjson-c:${PKGS} AS pkg-libjson-c
FROM ghcr.io/talos-systems/libpopt:${PKGS} AS pkg-libpopt
FROM ghcr.io/talos-systems/libressl:${PKGS} AS pkg-libressl
FROM ghcr.io/talos-systems/libseccomp:${PKGS} AS pkg-libseccomp
FROM ghcr.io/talos-systems/linux-firmware:${PKGS} AS pkg-linux-firmware
Expand Down Expand Up @@ -358,6 +360,8 @@ COPY --from=pkg-containerd / /rootfs
COPY --from=pkg-dosfstools / /rootfs
COPY --from=pkg-eudev / /rootfs
COPY --from=pkg-iptables / /rootfs
COPY --from=pkg-libjson-c / /rootfs
COPY --from=pkg-libpopt / /rootfs
COPY --from=pkg-libressl / /rootfs
COPY --from=pkg-libseccomp / /rootfs
COPY --from=pkg-linux-firmware /lib/firmware/bnx2 /rootfs/lib/firmware/bnx2
Expand Down
1 change: 0 additions & 1 deletion cmd/installer/pkg/install/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,6 @@ func (m *Manifest) zeroDevice(device Device) (err error) {
func (t *Target) Partition(pt *gpt.GPT, pos int, bd *blockdevice.BlockDevice) (err error) {
if t.Skip {
part := pt.Partitions().FindByName(t.Label)

if part != nil {
log.Printf("skipped %s (%s) size %d blocks", t.PartitionName, t.Label, part.Length())
}
Expand Down
109 changes: 64 additions & 45 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

humanize "github.com/dustin/go-humanize"
"github.com/spf13/cobra"
"github.com/talos-systems/go-blockdevice/blockdevice/encryption"
talosnet "github.com/talos-systems/net"
"k8s.io/client-go/tools/clientcmd"

Expand All @@ -42,51 +43,52 @@ import (
)

var (
talosconfig string
nodeImage string
nodeInstallImage string
registryMirrors []string
registryInsecure []string
kubernetesVersion string
nodeVmlinuzPath string
nodeInitramfsPath string
nodeISOPath string
nodeDiskImagePath string
applyConfigEnabled bool
bootloaderEnabled bool
uefiEnabled bool
configDebug bool
networkCIDR string
networkMTU int
networkIPv4 bool
networkIPv6 bool
wireguardCIDR string
nameservers []string
dnsDomain string
workers int
masters int
clusterCpus string
clusterMemory int
clusterDiskSize int
clusterDisks []string
targetArch string
clusterWait bool
clusterWaitTimeout time.Duration
forceInitNodeAsEndpoint bool
forceEndpoint string
inputDir string
cniBinPath []string
cniConfDir string
cniCacheDir string
cniBundleURL string
ports string
dockerHostIP string
withInitNode bool
customCNIUrl string
crashdumpOnFailure bool
skipKubeconfig bool
skipInjectingConfig bool
talosVersion string
talosconfig string
nodeImage string
nodeInstallImage string
registryMirrors []string
registryInsecure []string
kubernetesVersion string
nodeVmlinuzPath string
nodeInitramfsPath string
nodeISOPath string
nodeDiskImagePath string
applyConfigEnabled bool
bootloaderEnabled bool
uefiEnabled bool
configDebug bool
networkCIDR string
networkMTU int
networkIPv4 bool
networkIPv6 bool
wireguardCIDR string
nameservers []string
dnsDomain string
workers int
masters int
clusterCpus string
clusterMemory int
clusterDiskSize int
clusterDisks []string
targetArch string
clusterWait bool
clusterWaitTimeout time.Duration
forceInitNodeAsEndpoint bool
forceEndpoint string
inputDir string
cniBinPath []string
cniConfDir string
cniCacheDir string
cniBundleURL string
ports string
dockerHostIP string
withInitNode bool
customCNIUrl string
crashdumpOnFailure bool
skipKubeconfig bool
skipInjectingConfig bool
talosVersion string
encryptEphemeralPartition bool
)

// createCmd represents the cluster up command.
Expand Down Expand Up @@ -304,6 +306,22 @@ func create(ctx context.Context) (err error) {
genOptions = append(genOptions, generate.WithVersionContract(versionContract))
}

if encryptEphemeralPartition {
genOptions = append(genOptions, generate.WithSystemDiskEncryption(
&v1alpha1.SystemDiskEncryptionConfig{
EphemeralPartition: &v1alpha1.EncryptionConfig{
EncryptionProvider: encryption.LUKS2,
EncryptionKeys: []*v1alpha1.EncryptionKey{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
},
},
},
))
}

defaultInternalLB, defaultEndpoint := provisioner.GetLoadBalancers(request.Network)

if defaultInternalLB == "" {
Expand Down Expand Up @@ -701,6 +719,7 @@ func init() {
createCmd.Flags().BoolVar(&crashdumpOnFailure, "crashdump", false, "print debug crashdump to stderr when cluster startup fails")
createCmd.Flags().BoolVar(&skipKubeconfig, "skip-kubeconfig", false, "skip merging kubeconfig from the created cluster")
createCmd.Flags().BoolVar(&skipInjectingConfig, "skip-injecting-config", false, "skip injecting config from embedded metadata server, write config files to current directory")
createCmd.Flags().BoolVar(&encryptEphemeralPartition, "encrypt-ephemeral", false, "enable ephemeral partition encryption")
createCmd.Flags().StringVar(&talosVersion, "talos-version", "", "the desired Talos version to generate config for (if not set, defaults to image version)")
Cmd.AddCommand(createCmd)
}
15 changes: 13 additions & 2 deletions hack/test/e2e-qemu.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ source ./hack/test/e2e.sh

PROVISIONER=qemu
CLUSTER_NAME=e2e-${PROVISIONER}
CLUSTER_CIDR=${CLUSTER_CIDR:-1}

case "${CI:-false}" in
true)
Expand Down Expand Up @@ -45,6 +46,15 @@ case "${USE_DISK_IMAGE:-false}" in
;;
esac

case "${WITH_DISK_ENCRYPTION:-false}" in
false)
DISK_ENCRYPTION_FLAG=""
;;
*)
DISK_ENCRYPTION_FLAG="--encrypt-ephemeral"
;;
esac

function create_cluster {
build_registry_mirrors

Expand All @@ -55,19 +65,20 @@ function create_cluster {
--mtu 1450 \
--memory 2048 \
--cpus 2.0 \
--cidr 172.20.1.0/24 \
--cidr 172.20.${CLUSTER_CIDR}.0/24 \
--user-disk /var/lib/extra:100MB \
--user-disk /var/lib/p1:100MB:/var/lib/p2:100MB \
--install-image ${REGISTRY:-ghcr.io}/talos-systems/installer:${INSTALLER_TAG} \
--with-init-node=false \
--cni-bundle-url ${ARTIFACTS}/talosctl-cni-bundle-'${ARCH}'.tar.gz \
--crashdump \
${DISK_IMAGE_FLAG} \
${DISK_ENCRYPTION_FLAG} \
${REGISTRY_MIRROR_FLAGS} \
${QEMU_FLAGS} \
${CUSTOM_CNI_FLAG}

"${TALOSCTL}" config node 172.20.1.2
"${TALOSCTL}" config node 172.20.${CLUSTER_CIDR}.2
}

function destroy_cluster() {
Expand Down
124 changes: 124 additions & 0 deletions internal/integration/api/apply-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/talos-systems/talos/pkg/machinery/client"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
Expand Down Expand Up @@ -179,6 +180,129 @@ func (suite *ApplyConfigSuite) TestApplyOnReboot() {
suite.Require().NoError(err, "failed to apply deferred configuration (node %q): %w", node)
}

// TestApplyConfigRotateEncryptionSecrets verify key rotation by sequential apply config calls.
func (suite *ApplyConfigSuite) TestApplyConfigRotateEncryptionSecrets() {
suite.T().Skip("skip: this test is not stable yet")

node := suite.RandomDiscoveredNode(machine.TypeJoin)
suite.ClearConnectionRefused(suite.ctx, node)

nodeCtx := client.WithNodes(suite.ctx, node)
provider, err := suite.readConfigFromNode(nodeCtx)

suite.Assert().NoError(err)

encryption := provider.Machine().SystemDiskEncryption().Get(constants.EphemeralPartitionLabel)

if encryption == nil {
suite.T().Skip("skipped in not encrypted mode")
}

suite.WaitForBootDone(suite.ctx)

cfg, ok := encryption.(*v1alpha1.EncryptionConfig)
suite.Assert().True(ok)

keySets := [][]*v1alpha1.EncryptionKey{
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
},
},
{
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
},
},
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "AlO93jayutOpsDxDS=-",
},
KeySlot: 1,
},
},
{
{
KeyNodeID: &v1alpha1.EncryptionKeyNodeID{},
KeySlot: 0,
},
{
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "1js4nfhvneJJsak=GVN4Inf5gh",
},
KeySlot: 1,
},
},
}

for _, keys := range keySets {
cfg.EncryptionKeys = keys

data, err := provider.Bytes()
suite.Require().NoError(err)

suite.AssertRebooted(suite.ctx, node, func(nodeCtx context.Context) error {
_, err = suite.Client.ApplyConfiguration(nodeCtx, &machineapi.ApplyConfigurationRequest{
Data: data,
})
if err != nil {
// It is expected that the connection will EOF here, so just log the error
suite.Assert().Nilf("failed to apply configuration (node %q): %w", node, err)
}

return nil
}, assertRebootedRebootTimeout)

// Verify configuration change
var newProvider config.Provider

suite.Require().Nilf(retry.Constant(time.Minute, retry.WithUnits(time.Second)).Retry(func() error {
newProvider, err = suite.readConfigFromNode(nodeCtx)
if err != nil {
return retry.ExpectedError(err)
}

return nil
}), "failed to read updated configuration from node %q: %w", node, err)

e := newProvider.Machine().SystemDiskEncryption().Get(constants.EphemeralPartitionLabel)

for i, k := range e.Keys() {
if keys[i].KeyStatic == nil {
suite.Require().Nil(k.Static())
} else {
suite.Require().Equal(keys[i].Static().Key(), k.Static().Key())
}

if keys[i].KeyNodeID == nil {
suite.Require().Nil(k.NodeID())
} else {
suite.Require().NotNil(keys[i].NodeID())
}

suite.Require().Equal(keys[i].Slot(), k.Slot())
suite.Require().Equal(keys[i].Slot(), k.Slot())
}

suite.WaitForBootDone(suite.ctx)
}
}

func (suite *ApplyConfigSuite) readConfigFromNode(nodeCtx context.Context) (config.Provider, error) {
// Load the current node machine config
cfgData := new(bytes.Buffer)
Expand Down

0 comments on commit 58ff2c9

Please sign in to comment.