Skip to content

Commit

Permalink
config/v3_4_exp: add Tang offline provisioning support
Browse files Browse the repository at this point in the history
Expand schema for 3_4_exp with an advertisement field to allow for
Ignition to support Tang offline provisioning by passing the supplied
advertisement field during first boot's device bind. Fixes coreos#1474
  • Loading branch information
prestist committed Feb 18, 2023
1 parent d47fe07 commit a6273d3
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 23 deletions.
3 changes: 3 additions & 0 deletions config/v3_4_experimental/schema/ignition.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@
},
"thumbprint": {
"type": ["string", "null"]
},
"advertisement": {
"type": ["string", "null"]
}
}
},
Expand Down
8 changes: 8 additions & 0 deletions config/v3_4_experimental/translate/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func translateDirectoryEmbedded1(old old_types.DirectoryEmbedded1) (ret types.Di

func translateLuks(old old_types.Luks) (ret types.Luks) {
tr := translate.NewTranslator()
tr.AddCustomTranslator(translateTang)
tr.Translate(&old.Clevis, &ret.Clevis)
tr.Translate(&old.Device, &ret.Device)
tr.Translate(&old.KeyFile, &ret.KeyFile)
Expand All @@ -66,6 +67,13 @@ func translateLuks(old old_types.Luks) (ret types.Luks) {
return
}

func translateTang(old old_types.Tang) (ret types.Tang) {
tr := translate.NewTranslator()
tr.Translate(&old.Thumbprint, &ret.Thumbprint)
tr.Translate(&old.URL, &ret.URL)
return
}

func Translate(old old_types.Config) (ret types.Config) {
tr := translate.NewTranslator()
tr.AddCustomTranslator(translateIgnition)
Expand Down
5 changes: 3 additions & 2 deletions config/v3_4_experimental/types/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ type TLS struct {
}

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

type Timeouts struct {
Expand Down
1 change: 1 addition & 0 deletions docs/configuration-v3_4_experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ The Ignition configuration is a JSON document conforming to the following specif
* **_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.
* **advertisement** (string): the advertisement JSON. If not specified, the advertisement is fetched from the tang server during provisioning.
* **_tpm2_** (bool): whether or not to use a tpm2 device.
* **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1.
* **_custom_** (object): overrides the clevis configuration. The `pin` & `config` will be passed directly to `clevis luks bind`. If specified, all other clevis options must be omitted.
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ nav_order: 9
- Ship aarch64 macOS ignition-validate binary in GitHub release artifacts
- Allow enabling discard passthrough on LUKS devices _(3.4.0-exp)_
- Allow specifying arbitrary LUKS open options _(3.4.0-exp)_
- Support offline Tang provisioning via pre-shared advertisement _(3.4.0-exp)_

### Changes

Expand Down
66 changes: 46 additions & 20 deletions internal/exec/stages/disks/luks.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ var (

// 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"`
URL string `json:"url"`
Thumbprint string `json:"thp,omitempty"`
Advertisement any `json:"adv,omitempty"`
}

// https://github.com/latchset/clevis/blob/master/README.md#pin-shamir-secret-sharing
Expand Down Expand Up @@ -273,9 +274,17 @@ func (s *stage) createLuks(config types.Config) error {
c.Threshold = *luks.Clevis.Threshold
}
for _, tang := range luks.Clevis.Tang {
var adv any
if tang.Advertisement != nil {
err := json.Unmarshal([]byte(*tang.Advertisement), &adv)
if err != nil {
return fmt.Errorf("unmarshalling advertisement: %v", err)
}
}
c.Pins.Tang = append(c.Pins.Tang, Tang{
URL: tang.URL,
Thumbprint: *tang.Thumbprint,
URL: tang.URL,
Thumbprint: *tang.Thumbprint,
Advertisement: adv,
})
}
if luks.Clevis.Tpm2 != nil {
Expand All @@ -294,36 +303,53 @@ func (s *stage) createLuks(config types.Config) error {
// pass the device to clevis. We have to loop each device as
// the devices could be on different NICs that haven't come
// up yet.

// A running count of tang servers without an advertisement
tangServersWithoutAdv := 0
for _, tang := range luks.Clevis.Tang {
u, err := url.Parse(tang.URL)
if err != nil {
return fmt.Errorf("parsing tang URL: %v", err)
}
u.Path = path.Join(u.Path, "adv")
_, err = s.Fetcher.FetchToBuffer(*u, resource.FetchOptions{})
if err != nil {
return fmt.Errorf("fetching tang advertisement: %v", err)
if util.NilOrEmpty(tang.Advertisement) {
tangServersWithoutAdv++
u.Path = path.Join(u.Path, "adv")
_, err = s.Fetcher.FetchToBuffer(*u, resource.FetchOptions{})
if err != nil {
return fmt.Errorf("fetching tang advertisement: %v", err)
}
}
}

// lets bind our device
if _, err := s.Logger.LogCmd(
exec.Command(distro.ClevisCmd(), "luks", "bind", "-f", "-k", keyFilePath, "-d", devAlias, pin, config), "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)
intTpm2 := 0
if util.IsTrue(luks.Clevis.Tpm2) {
intTpm2 = 1
}
if _, err := s.Logger.LogCmd(
exec.Command(distro.ClevisCmd(), "luks", "unlock", "-d", devAlias, "-n", luks.Name),
"reopening clevis luks device %s", luks.Name,
); err != nil {
return fmt.Errorf("reopening luks device %s: %v", luks.Name, err)
threshold := 1
if luks.Clevis.Threshold != nil {
threshold = *luks.Clevis.Threshold
}
// Check if we can safely close and re-open the device
if tangServersWithoutAdv+intTpm2 >= threshold {
// 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", devAlias, "-n", luks.Name),
"reopening clevis luks device %s", luks.Name,
); err != nil {
return fmt.Errorf("reopening luks device %s: %v", luks.Name, err)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions internal/exec/stages/fetch_offline/fetch-offline.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func configNeedsNetRecurse(v reflect.Value) (bool, error) {
case t == reflect.TypeOf(types.Resource{}):
return sourceNeedsNet(v.Interface().(types.Resource))
case t == reflect.TypeOf(types.Tang{}):
tang := v.Interface().(types.Tang)
if !cfgutil.NilOrEmpty(tang.Advertisement) {
return false, nil
}
return true, nil
case t == reflect.TypeOf(types.ClevisCustom{}):
cc := v.Interface().(types.ClevisCustom)
Expand Down
92 changes: 91 additions & 1 deletion internal/exec/stages/fetch_offline/fetch_offline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ func TestConfigNotNeedsNet(t *testing.T) {
},
// Empty Config
{},
// Tang with adv set does not need networking on first boot.
{
Storage: types.Storage{
Luks: []types.Luks{
{
Name: "foobar",
Device: util.StrToPtr("foo"),
Clevis: types.Clevis{
Tang: []types.Tang{
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: util.StrToPtr(" {\"payload\": \"...\",\"protected\":\"...\",\"signature\":\"...\"}"),
},
},
},
},
},
},
},
}

for i, test := range tests {
Expand All @@ -80,7 +100,7 @@ func TestConfigNotNeedsNet(t *testing.T) {

func TestConfigNeedsNet(t *testing.T) {
tests := []types.Config{
// Tang
// Tang with no adv set needs networking on first boot.
{
Storage: types.Storage{
Luks: []types.Luks{
Expand Down Expand Up @@ -126,6 +146,76 @@ func TestConfigNeedsNet(t *testing.T) {
},
},
},
// Tang with adv explicitly set to nil needs networking on first boot.
{
Storage: types.Storage{
Luks: []types.Luks{
{
Name: "foobar",
Device: util.StrToPtr("foo"),
Clevis: types.Clevis{
Tang: []types.Tang{
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: nil,
},
},
},
},
},
},
},
// Multiple Tangs; one with adv set, one without needs networking on first boot.
{
Storage: types.Storage{
Luks: []types.Luks{
{
Name: "foobar",
Device: util.StrToPtr("foo"),
Clevis: types.Clevis{
Tang: []types.Tang{
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: util.StrToPtr(" {\"payload\": \"...\",\"protected\":\"...\",\"signature\":\"...\"}"),
},
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: nil,
},
},
},
},
},
},
},
// Multiple Tangs with no adv set needs networking on first boot.
{
Storage: types.Storage{
Luks: []types.Luks{
{
Name: "foobar",
Device: util.StrToPtr("foo"),
Clevis: types.Clevis{
Tang: []types.Tang{
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: util.StrToPtr(""),
},
{
Thumbprint: util.StrToPtr("mythumbprint"),
URL: "http://mytang.example.com",
Advertisement: nil,
},
},
},
},
},
},
},
}

for i, test := range tests {
Expand Down

0 comments on commit a6273d3

Please sign in to comment.