Skip to content

Commit

Permalink
internal: add LUKS support
Browse files Browse the repository at this point in the history
  • Loading branch information
arithx committed Jul 8, 2020
1 parent 5e91412 commit f41dcd7
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 13 deletions.
9 changes: 9 additions & 0 deletions internal/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ var (
chccwdevCmd = "chccwdev"
cioIgnoreCmd = "cio_ignore"

// LUKS programs
clevisCmd = "clevis"
cryptsetupCmd = "cryptsetup"
ddCmd = "dd"

// Flags
selinuxRelabel = "true"
blackboxTesting = "false"
Expand Down Expand Up @@ -94,6 +99,10 @@ func VmurCmd() string { return vmurCmd }
func ChccwdevCmd() string { return chccwdevCmd }
func CioIgnoreCmd() string { return cioIgnoreCmd }

func ClevisCmd() string { return clevisCmd }
func CryptsetupCmd() string { return cryptsetupCmd }
func DdCmd() string { return ddCmd }

func SelinuxRelabel() bool { return bakedStringToBool(selinuxRelabel) && !BlackboxTesting() }
func BlackboxTesting() bool { return bakedStringToBool(blackboxTesting) }
func WriteAuthorizedKeysFragment() bool {
Expand Down
7 changes: 6 additions & 1 deletion internal/exec/stages/disks/disks.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ func (s stage) Run(config types.Config) error {
// do the udevadm settle and can just return here.
if len(config.Storage.Disks) == 0 &&
len(config.Storage.Raid) == 0 &&
len(config.Storage.Filesystems) == 0 {
len(config.Storage.Filesystems) == 0 &&
len(config.Storage.Luks) == 0 {
return nil
}

Expand All @@ -83,6 +84,10 @@ func (s stage) Run(config types.Config) error {
return fmt.Errorf("failed to create raids: %v", err)
}

if err := s.createLuks(config); err != nil {
return fmt.Errorf("failed to create luks: %v", err)
}

if err := s.createFilesystems(config); err != nil {
return fmt.Errorf("failed to create filesystems: %v", err)
}
Expand Down
217 changes: 217 additions & 0 deletions internal/exec/stages/disks/luks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright 2020 Red Hat
//
// 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.

// The storage stage is responsible for partitioning disks, creating RAID
// arrays, formatting partitions, writing files, writing systemd units, and
// writing network units.
// createRaids creates the raid arrays described in config.Storage.Raid.

package disks

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"

"github.com/coreos/ignition/v2/config/util"
"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
"github.com/coreos/ignition/v2/internal/distro"
execUtil "github.com/coreos/ignition/v2/internal/exec/util"
)

// https://github.com/latchset/clevis/blob/master/src/pins/tang/clevis-encrypt-tang.1.adoc#config
type Tang struct {
URL string `json:"url"`
Thumbprint string `json:"thp,omitempty"`
}

// https://github.com/latchset/clevis/blob/master/README.md#pin-shamir-secret-sharing
type Pin struct {
Tpm bool `json:"tpm"`
Tang []Tang `json:"tang,omitempty"`
}

func (p Pin) MarshalJSON() ([]byte, error) {
if p.Tpm {
return json.Marshal(&struct {
Tang []Tang `json:"tang,omitempty"`
Tpm struct{} `json:"tpm2"`
}{
Tang: p.Tang,
Tpm: struct{}{},
})
}
return json.Marshal(&struct {
Tang []Tang `json:"tang"`
}{
Tang: p.Tang,
})
}

type Clevis struct {
Pins Pin `json:"pins"`
Threshold int `json:"t"`
}

// Initially tested generating keyfiles via dd'ing to a file from /dev/urandom
// however while cryptsetup had no problem with these keyfiles clevis seemed to
// die on them while keyfiles generated via openssl rand -hex would work...
func randHex(length int) (string, error) {
bytes := make([]byte, length)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}

func (s *stage) createLuks(config types.Config) error {
if len(config.Storage.Luks) == 0 {
return nil
}

for _, luks := range config.Storage.Luks {
// TODO: allow Ignition generated KeyFiles for
// non-clevis devices that can be persisted.
// TODO: create devices in parallel.
// track whether Ignition creates the KeyFile
// so that it can be removed creation
var ignitionCreatedKeyFile bool
// create keyfile inside of tmpfs, it will be copied to the
// sysroot by the files stage
os.MkdirAll(execUtil.LuksInitramfsKeyFilePath, 0644)
keyFilePath := filepath.Join(execUtil.LuksInitramfsKeyFilePath, luks.Name)
if luks.KeyFile == nil || *luks.KeyFile == "" {
// create a keyfile
key, err := randHex(4096)
if err != nil {
return fmt.Errorf("generating keyfile: %v", err)
}
if err := ioutil.WriteFile(keyFilePath, []byte(key), 0400); err != nil {
return fmt.Errorf("creating keyfile: %v", err)
}
ignitionCreatedKeyFile = true
} else {
if err := ioutil.WriteFile(keyFilePath, []byte(*luks.KeyFile), 0400); err != nil {
return fmt.Errorf("writing keyfile: %v", err)
}
}

args := []string{
"luksFormat",
"--type", "luks2",
"--key-file", keyFilePath,
}

if !util.NilOrEmpty(luks.Hash) {
args = append(args, "--hash", *luks.Hash)
}

if !util.NilOrEmpty(luks.Label) {
args = append(args, "--label", *luks.Label)
}

if !util.NilOrEmpty(luks.UUID) {
args = append(args, "--uuid", *luks.UUID)
}

if !util.NilOrEmpty(luks.Cipher) {
args = append(args, "--cipher", *luks.Cipher)
}

if len(luks.Options) > 0 {
// golang's a really great language...
for _, option := range luks.Options {
args = append(args, string(option))
}
}

args = append(args, luks.Device)

if _, err := s.Logger.LogCmd(
exec.Command(distro.CryptsetupCmd(), args...),
"creating %q", luks.Name,
); err != nil {
return fmt.Errorf("cryptsetup failed: %v", err)
}

// open the device
if _, err := s.Logger.LogCmd(
exec.Command(distro.CryptsetupCmd(), "luksOpen", luks.Device, luks.Name, "--key-file", keyFilePath),
"opening luks device %v", luks.Name,
); err != nil {
return fmt.Errorf("opening luks device: %v", err)
}

if luks.Clevis != nil {
c := Clevis{
Pins: Pin{},
}
if luks.Clevis.Threshold == nil {
c.Threshold = 1
} else {
c.Threshold = *luks.Clevis.Threshold
}
for _, tang := range luks.Clevis.Tang {
c.Pins.Tang = append(c.Pins.Tang, Tang{
URL: tang.URL,
Thumbprint: tang.Thumbprint,
})
}
if luks.Clevis.Tpm2 != nil {
c.Pins.Tpm = *luks.Clevis.Tpm2
}
clevisJson, err := json.Marshal(c)
if err != nil {
return fmt.Errorf("creating clevis json: %v", err)
}
if _, err := s.Logger.LogCmd(
exec.Command(distro.ClevisCmd(), "luks", "bind", "-f", "-k", keyFilePath, "-d", luks.Device, "sss", string(clevisJson)), "Clevis bind",
); err != nil {
return fmt.Errorf("binding clevis device: %v", err)
}

// close & re-open Clevis devices to make sure that we can unlock them
if _, err := s.Logger.LogCmd(
exec.Command(distro.CryptsetupCmd(), "luksClose", luks.Name),
"closing clevis luks device %v", luks.Name,
); err != nil {
return fmt.Errorf("closing luks device: %v", err)
}
if _, err := s.Logger.LogCmd(
exec.Command(distro.ClevisCmd(), "luks", "unlock", "-d", luks.Device, "-n", luks.Name),
"opening clevis luks device %s", luks.Name,
); err != nil {
return fmt.Errorf("opening luks device %s: %v", luks.Name, err)
}
}

// assume the user does not want a key file, remove it
if ignitionCreatedKeyFile {
if _, err := s.Logger.LogCmd(
exec.Command(distro.CryptsetupCmd(), "luksRemoveKey", luks.Device, keyFilePath),
"removing key file for %v", luks.Name,
); err != nil {
return fmt.Errorf("removing key file: %v", err)
}
os.Remove(keyFilePath)
}
}

return nil
}
4 changes: 4 additions & 0 deletions internal/exec/stages/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func (s stage) Run(config types.Config) error {
return fmt.Errorf("failed to create units: %v", err)
}

if err := s.createCrypttabEntries(config); err != nil {
return fmt.Errorf("creating crypttab entries: %v", err)
}

if err := s.relabelFiles(); err != nil {
return fmt.Errorf("failed to handle relabeling: %v", err)
}
Expand Down
87 changes: 75 additions & 12 deletions internal/exec/stages/files/filesystemEntries.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,90 @@ package files

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"

"github.com/coreos/ignition/v2/config/v3_2_experimental/types"
"github.com/coreos/ignition/v2/internal/distro"
"github.com/coreos/ignition/v2/internal/exec/util"
"github.com/coreos/ignition/v2/internal/log"

"github.com/vincent-petithory/dataurl"
)

// createCrypttabEntries creates entries inside of /etc/crypttab for LUKS volumes,
// as well as copying keyfiles to the sysroot.
func (s *stage) createCrypttabEntries(config types.Config) error {
if len(config.Storage.Luks) == 0 {
return nil
}

s.Logger.PushPrefix("createCrypttabEntries")
defer s.Logger.PopPrefix()

crypttab := fileEntry{
types.Node{
Path: "/sysroot/etc/crypttab",
},
types.FileEmbedded1{},
}
keyfiles := []fileEntry{}
for _, luks := range config.Storage.Luks {
out, err := exec.Command(distro.CryptsetupCmd(), "luksUUID", luks.Device).CombinedOutput()
if err != nil {
return fmt.Errorf("gathering luks uuid: %s: %v", out, err)
}
uuid := strings.TrimSpace(string(out))
netdev := ""
if len(luks.Clevis.Tang) > 0 {
netdev = ",_netdev"
}
keyfile := "none"
if luks.Clevis == nil {
keyfile = filepath.Join(util.LuksRealRootKeyFilePath, luks.Name)

// Copy keyfile from /run to sysroot
contents, err := ioutil.ReadFile(filepath.Join(util.LuksInitramfsKeyFilePath, luks.Name))
if err != nil {
return fmt.Errorf("reading keyfile for %s: %v", luks.Name, err)
}
contentsUri := dataurl.EncodeBytes(contents)
keyfiles = append(keyfiles, fileEntry{
types.Node{
Path: filepath.Join("/sysroot", keyfile),
},
types.FileEmbedded1{
Contents: types.Resource{
Source: &contentsUri,
},
},
})
}
uri := dataurl.EncodeBytes([]byte(fmt.Sprintf("%s UUID=%s %s luks%s\n", luks.Name, uuid, keyfile, netdev)))
crypttab.Append = append(crypttab.Append, types.Resource{
Source: &uri,
})
}
if err := crypttab.create(s.Logger, s.Util); err != nil {
return fmt.Errorf("adding luks devices to crypttab: %v", err)
}
for _, file := range keyfiles {
if err := file.create(s.Logger, s.Util); err != nil {
return fmt.Errorf("copying keyfile: %v", err)
}
}
// delete the entire keyfiles folder in /run/ so that the keyfiles are stored on
// only the root device which can be encrypted
if err := os.RemoveAll(util.LuksInitramfsKeyFilePath); err != nil {
return fmt.Errorf("removing initramfs keyfiles: %v", err)
}
return nil
}

// createFilesystemsEntries creates the files described in config.Storage.{Files,Directories}.
func (s *stage) createFilesystemsEntries(config types.Config) error {
s.Logger.PushPrefix("createFilesystemsFiles")
Expand Down Expand Up @@ -225,7 +299,6 @@ func (s stage) getOrderedCreationList(config types.Config) ([]filesystemEntry, e
entries = append(entries, fileEntry(f))
}

hardlinks := []filesystemEntry{}
for _, l := range config.Storage.Links {
path, err := s.JoinPath(l.Path)
if err != nil {
Expand All @@ -237,20 +310,10 @@ func (s stage) getOrderedCreationList(config types.Config) ([]filesystemEntry, e
}
paths[path] = l.Path
l.Path = path
if l.Hard != nil && *l.Hard {
hardlinks = append(hardlinks, linkEntry(l))
} else {
entries = append(entries, linkEntry(l))
}

entries = append(entries, linkEntry(l))
}
sort.Slice(entries, func(i, j int) bool { return util.Depth(entries[i].node().Path) < util.Depth(entries[j].node().Path) })

// Append all the hard links to the list after sorting. This allows
// Ignition to create hard links to files that are deeper than the hard
// link. For reference: https://github.com/coreos/ignition/issues/800
entries = append(entries, hardlinks...)

return entries, nil
}

Expand Down
Loading

0 comments on commit f41dcd7

Please sign in to comment.