Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: add LUKS #960

Merged
merged 3 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/shared/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ var (
ErrXfsLabelTooLong = errors.New("filesystem labels cannot be longer than 12 characters when using xfs")
ErrSwapLabelTooLong = errors.New("filesystem labels cannot be longer than 15 characters when using swap")
ErrVfatLabelTooLong = errors.New("filesystem labels cannot be longer than 11 characters when using vfat")
ErrLuksLabelTooLong = errors.New("luks device labels cannot be longer than 47 characters")
ErrLuksNameContainsSlash = errors.New("device names cannot contain slashes")
ErrInvalidLuksVolume = errors.New("a key-file or clevis configuration must be specified")
ErrTangThumbprintRequired = errors.New("thumbprint is required")
ErrFileIllegalMode = errors.New("illegal file mode")
ErrBothIDAndNameSet = errors.New("cannot set both id and name")
ErrLabelTooLong = errors.New("partition labels may not exceed 36 characters")
Expand Down
63 changes: 63 additions & 0 deletions config/v3_2_experimental/schema/ignition.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@
"$ref": "#/definitions/storage/definitions/raid"
}
},
"luks": {
"type": "array",
"items": {
"$ref": "#/definitions/storage/definitions/luks"
}
},
"filesystems": {
"type": "array",
"items": {
Expand Down Expand Up @@ -232,6 +238,63 @@
"devices"
]
},
"luks": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"label": {
"type": ["string", "null"]
},
"uuid": {
"type": ["string", "null"]
},
"device": {
"type": ["string", "null"]
},
"keyFile": {
"$ref": "#/definitions/resource"
},
"clevis": {
"type": ["object", "null"],
"properties": {
"tpm2": {
"type": ["boolean", "null"]
},
"tang": {
"type": "array",
"items": {
"$ref": "#/definitions/storage/definitions/tang"
}
},
"threshold": {
"type": ["integer", "null"]
}
}
},
"options": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name"
]
},
"tang": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"thumbprint": {
"type": ["string", "null"]
}
}
},
"filesystem": {
"type": "object",
"properties": {
Expand Down
12 changes: 12 additions & 0 deletions config/v3_2_experimental/translate/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,21 @@ func translateIgnition(old old_types.Ignition) (ret types.Ignition) {
return
}

func translateStorage(old old_types.Storage) (ret types.Storage) {
tr := translate.NewTranslator()
tr.Translate(&old.Directories, &ret.Directories)
tr.Translate(&old.Disks, &ret.Disks)
tr.Translate(&old.Files, &ret.Files)
tr.Translate(&old.Filesystems, &ret.Filesystems)
tr.Translate(&old.Links, &ret.Links)
tr.Translate(&old.Raid, &ret.Raid)
return
arithx marked this conversation as resolved.
Show resolved Hide resolved
}

func Translate(old old_types.Config) (ret types.Config) {
tr := translate.NewTranslator()
tr.AddCustomTranslator(translateIgnition)
tr.AddCustomTranslator(translateStorage)
tr.Translate(&old, &ret)
return
}
75 changes: 75 additions & 0 deletions config/v3_2_experimental/types/luks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2020 Red Hat, Inc.
//
// 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 types

import (
"strings"

"github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/config/util"

"github.com/coreos/vcontext/path"
"github.com/coreos/vcontext/report"
)

func (l Luks) Key() string {
return l.Name
}

func (l Luks) IgnoreDuplicates() map[string]struct{} {
return map[string]struct{}{
"Options": {},
}
}

func (l Luks) Validate(c path.ContextPath) (r report.Report) {
if strings.Contains(l.Name, "/") {
r.AddOnError(c.Append("name"), errors.ErrLuksNameContainsSlash)
}
r.AddOnError(c.Append("label"), l.validateLabel())
if util.NilOrEmpty(l.Device) {
r.AddOnError(c.Append("device"), errors.ErrDiskDeviceRequired)
} else {
r.AddOnError(c.Append("device"), validatePath(*l.Device))
}

// fail if there is no valid keyfile & no clevis entries
if err := l.KeyFile.validateRequiredSource(); err != nil && l.emptyClevis() {
r.AddOnError(c.Append("keys"), errors.ErrInvalidLuksVolume)
}
return
}

func (l Luks) emptyClevis() bool {
if l.Clevis == nil {
return true
}

return len(l.Clevis.Tang) == 0 && (l.Clevis.Tpm2 == nil || !*l.Clevis.Tpm2)
}

func (l Luks) validateLabel() error {
if util.NilOrEmpty(l.Label) {
return nil
}

if len(*l.Label) > 47 {
// LUKS2_LABEL_L has a maximum length of 48 (including the null terminator)
// https://gitlab.com/cryptsetup/cryptsetup/-/blob/1633f030e89ad2f11ae649ba9600997a41abd3fc/lib/luks2/luks2.h#L86
return errors.ErrLuksLabelTooLong
}

return nil
}
24 changes: 24 additions & 0 deletions config/v3_2_experimental/types/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ package types

// generated by "schematyper --package=types config/v3_2_experimental/schema/ignition.json -o config/v3_2_experimental/types/schema.go --root-type=Config" -- DO NOT EDIT

type Clevis struct {
Tang []Tang `json:"tang,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not block on this, but I think fetch-offline should probably also check whether any Tang pins are present and SignalNeedNet() in that case. We can do that in a follow-up though!

Hmm, and then the rootmap code could also check if LUKS devices in the root block device tree are Tang-pinned and adds rd.neednet=1 to the BLS for subsequent boots.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, yeah, forgot about fetch-offline.

Threshold *int `json:"threshold,omitempty"`
Tpm2 *bool `json:"tpm2,omitempty"`
}

type Config struct {
Ignition Ignition `json:"ignition"`
Passwd Passwd `json:"passwd,omitempty"`
Expand Down Expand Up @@ -87,6 +93,18 @@ type LinkEmbedded1 struct {
Target string `json:"target"`
}

type Luks struct {
Clevis *Clevis `json:"clevis,omitempty"`
Device *string `json:"device,omitempty"`
KeyFile Resource `json:"keyFile,omitempty"`
Label *string `json:"label,omitempty"`
Name string `json:"name"`
Options []LuksOption `json:"options,omitempty"`
UUID *string `json:"uuid,omitempty"`
}

type LuksOption string

type MountOption string

type NoProxyItem string
Expand Down Expand Up @@ -182,6 +200,7 @@ type Storage struct {
Files []File `json:"files,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Links []Link `json:"links,omitempty"`
Luks []Luks `json:"luks,omitempty"`
Raid []Raid `json:"raid,omitempty"`
}

Expand All @@ -193,6 +212,11 @@ type TLS struct {
CertificateAuthorities []Resource `json:"certificateAuthorities,omitempty"`
}

type Tang struct {
Thumbprint *string `json:"thumbprint,omitempty"`
URL string `json:"url,omitempty"`
}

type Timeouts struct {
HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
HTTPTotal *int `json:"httpTotal,omitempty"`
Expand Down
51 changes: 51 additions & 0 deletions config/v3_2_experimental/types/tang.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2020 Red Hat, Inc.
//
// 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 types

import (
"net/url"

"github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/config/util"

"github.com/coreos/vcontext/path"
"github.com/coreos/vcontext/report"
)

func (t Tang) Key() string {
return t.URL
}

func (t Tang) Validate(c path.ContextPath) (r report.Report) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about allowing an anonymous thumbprint? I can go either way on this. Tang is based on zero knowledge; the Tang server doesn't know the secret. Some "sugar" could allow for querying for the thumbprint.

Another big of sugar would be check the Tang server at this point to check the thumbprint and bail on the operation if there's a mismatch before doing the encryption and handing off to Clevis.

Note: consider this comment a nit for future consideration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about allowing an anonymous thumbprint? I can go either way on this. Tang is based on zero knowledge; the Tang server doesn't know the secret. Some "sugar" could allow for querying for the thumbprint.

I'm not sure I'm following, are you suggesting that we allow just the specification of the server and auto trust it despite not being given a thumbprint? I'm not sure I'd be in favor of that. I think I'd rather put the extra workload on the user at config generation time to know the URL & thumbprint.

Another big of sugar would be check the Tang server at this point to check the thumbprint and bail on the operation if there's a mismatch before doing the encryption and handing off to Clevis.

Note: consider this comment a nit for future consideration.

I'd probably punt from the initial PR but I like the concept.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably punt from the initial PR but I like the concept.

Right :) Let's get this out of the door. Just an idea for future improvement.

r.AddOnError(c.Append("url"), validateTangURL(t.URL))
if util.NilOrEmpty(t.Thumbprint) {
r.AddOnError(c.Append("thumbprint"), errors.ErrTangThumbprintRequired)
}
return
}

func validateTangURL(s string) error {
u, err := url.Parse(s)
if err != nil {
return errors.ErrInvalidUrl
}

switch u.Scheme {
case "http", "https":
return nil
default:
return errors.ErrInvalidScheme
}
}
20 changes: 20 additions & 0 deletions doc/configuration-v3_2_experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,26 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_name_** (string): the group name of the owner.
* **target** (string): the target path of the link
* **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true.
* **_luks_** (list of objects): the list of luks devices to be created. Every device must have a unique `name`.
* **name** (string): the name of the luks device.
* **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks.
* **_keyFile_** (string): options related to the contents of the key file.
* **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3.
* **_source_** (string): the URL of the contents to append. Supported schemes are `http`, `https`, `tftp`, `s3`, `gs`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified.
* **_httpHeaders_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only.
* **name** (string): the header name.
* **_value_** (string): the header contents.
* **_verification_** (object): options related to the verification of the key file.
* **_hash_** (string): the hash of the contents, in the form `<type>-<value>` where type is either `sha512` or `sha256`.
* **_label_** (string): the label of the luks device.
* **_uuid_** (string): the uuid of the luks device.
* **_options_** (list of strings): any additional options to be passed to the cryptsetup utility.
* **_clevis_** (object): describes the clevis configuration for the luks device.
* **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`.
* **url** (string): url of the tang server.
* **thumbprint** (string): thumbprint of a trusted signing key.
* **_tpm2_** (bool): whether or not to use a tpm2 device.
* **_threshold_** (int): sets the minimum number of pieces required to decrypt the device.
* **_systemd_** (object): describes the desired state of the systemd units.
* **_units_** (list of objects): the list of systemd units.
* **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). Every unit must have a unique `name`.
Expand Down
13 changes: 13 additions & 0 deletions internal/distro/distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ var (
chccwdevCmd = "chccwdev"
cioIgnoreCmd = "cio_ignore"

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

// Flags
selinuxRelabel = "true"
blackboxTesting = "false"
Expand All @@ -64,6 +68,9 @@ var (
// ".ssh/authorized_keys.d/ignition" ("true"), or to
// ".ssh/authorized_keys" ("false").
writeAuthorizedKeysFragment = "true"

luksInitramfsKeyFilePath = "/run/ignition/luks-keyfiles/"
luksRealRootKeyFilePath = "/etc/luks/"
)

func DiskByIDDir() string { return diskByIDDir }
Expand Down Expand Up @@ -94,6 +101,12 @@ 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 LuksInitramfsKeyFilePath() string { return luksInitramfsKeyFilePath }
func LuksRealRootKeyFilePath() string { return luksRealRootKeyFilePath }

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 {
bgilbert marked this conversation as resolved.
Show resolved Hide resolved
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
Loading