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

Stabilize openshift 4.13.0; create openshift 4.14.0-experimental #438

Merged
merged 3 commits into from
Mar 14, 2023
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
6 changes: 4 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import (
openshift4_10 "github.com/coreos/butane/config/openshift/v4_10"
openshift4_11 "github.com/coreos/butane/config/openshift/v4_11"
openshift4_12 "github.com/coreos/butane/config/openshift/v4_12"
openshift4_13_exp "github.com/coreos/butane/config/openshift/v4_13_exp"
openshift4_13 "github.com/coreos/butane/config/openshift/v4_13"
openshift4_14_exp "github.com/coreos/butane/config/openshift/v4_14_exp"
openshift4_8 "github.com/coreos/butane/config/openshift/v4_8"
openshift4_9 "github.com/coreos/butane/config/openshift/v4_9"
r4e1_0 "github.com/coreos/butane/config/r4e/v1_0"
Expand Down Expand Up @@ -64,7 +65,8 @@ func init() {
RegisterTranslator("openshift", "4.10.0", openshift4_10.ToConfigBytes)
RegisterTranslator("openshift", "4.11.0", openshift4_11.ToConfigBytes)
RegisterTranslator("openshift", "4.12.0", openshift4_12.ToConfigBytes)
RegisterTranslator("openshift", "4.13.0-experimental", openshift4_13_exp.ToConfigBytes)
RegisterTranslator("openshift", "4.13.0", openshift4_13.ToConfigBytes)
RegisterTranslator("openshift", "4.14.0-experimental", openshift4_14_exp.ToConfigBytes)
RegisterTranslator("r4e", "1.0.0", r4e1_0.ToIgn3_3Bytes)
RegisterTranslator("r4e", "1.1.0-experimental", r4e1_1_exp.ToIgn3_4Bytes)
RegisterTranslator("rhcos", "0.1.0", unsupportedRhcosVariant)
Expand Down
48 changes: 48 additions & 0 deletions config/openshift/v4_13/result/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2021 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 result

import (
"github.com/coreos/ignition/v2/config/v3_2/types"
)

const (
MC_API_VERSION = "machineconfiguration.openshift.io/v1"
MC_KIND = "MachineConfig"
)

// We round-trip through JSON because Ignition uses `json` struct tags,
// so all struct tags need to be `json` even though we're ultimately
// writing YAML.

type MachineConfig struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
Spec Spec `json:"spec"`
}

type Metadata struct {
Name string `json:"name"`
Labels map[string]string `json:"labels,omitempty"`
}

type Spec struct {
Config types.Config `json:"config"`
KernelArguments []string `json:"kernelArguments,omitempty"`
Extensions []string `json:"extensions,omitempty"`
FIPS *bool `json:"fips,omitempty"`
KernelType *string `json:"kernelType,omitempty"`
}
39 changes: 39 additions & 0 deletions config/openshift/v4_13/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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 v4_13

import (
fcos "github.com/coreos/butane/config/fcos/v1_3"
)

const ROLE_LABEL_KEY = "machineconfiguration.openshift.io/role"

type Config struct {
fcos.Config `yaml:",inline"`
Metadata Metadata `yaml:"metadata"`
OpenShift OpenShift `yaml:"openshift"`
}

type Metadata struct {
Name string `yaml:"name"`
Labels map[string]string `yaml:"labels,omitempty"`
}

type OpenShift struct {
KernelArguments []string `yaml:"kernel_arguments"`
Extensions []string `yaml:"extensions"`
FIPS *bool `yaml:"fips"`
KernelType *string `yaml:"kernel_type"`
}
279 changes: 279 additions & 0 deletions config/openshift/v4_13/translate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// 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 v4_13

import (
"net/url"
"reflect"
"strings"

"github.com/coreos/butane/config/common"
"github.com/coreos/butane/config/openshift/v4_13/result"
cutil "github.com/coreos/butane/config/util"
"github.com/coreos/butane/translate"

"github.com/coreos/ignition/v2/config/util"
"github.com/coreos/ignition/v2/config/v3_2/types"
"github.com/coreos/vcontext/path"
"github.com/coreos/vcontext/report"
)

const (
// FIPS 140-2 doesn't allow the default XTS mode
fipsCipherOption = types.LuksOption("--cipher")
fipsCipherShortOption = types.LuksOption("-c")
fipsCipherArgument = types.LuksOption("aes-cbc-essiv:sha256")
)

// ToMachineConfig4_13Unvalidated translates the config to a MachineConfig. It also
// returns the set of translations it did so paths in the resultant config
// can be tracked back to their source in the source config. No config
// validation is performed on input or output.
func (c Config) ToMachineConfig4_13Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) {
cfg, ts, r := c.Config.ToIgn3_2Unvalidated(options)
if r.IsFatal() {
return result.MachineConfig{}, ts, r
}

// wrap
ts = ts.PrefixPaths(path.New("yaml"), path.New("json", "spec", "config"))
mc := result.MachineConfig{
ApiVersion: result.MC_API_VERSION,
Kind: result.MC_KIND,
Metadata: result.Metadata{
Name: c.Metadata.Name,
Labels: make(map[string]string),
},
Spec: result.Spec{
Config: cfg,
},
}
ts.AddTranslation(path.New("yaml", "version"), path.New("json", "apiVersion"))
ts.AddTranslation(path.New("yaml", "version"), path.New("json", "kind"))
ts.AddTranslation(path.New("yaml", "metadata"), path.New("json", "metadata"))
ts.AddTranslation(path.New("yaml", "metadata", "name"), path.New("json", "metadata", "name"))
ts.AddTranslation(path.New("yaml", "metadata", "labels"), path.New("json", "metadata", "labels"))
ts.AddTranslation(path.New("yaml", "version"), path.New("json", "spec"))
ts.AddTranslation(path.New("yaml"), path.New("json", "spec", "config"))
for k, v := range c.Metadata.Labels {
mc.Metadata.Labels[k] = v
ts.AddTranslation(path.New("yaml", "metadata", "labels", k), path.New("json", "metadata", "labels", k))
}

// translate OpenShift fields
tr := translate.NewTranslator("yaml", "json", options)
from := &c.OpenShift
to := &mc.Spec
ts2, r2 := translate.Prefixed(tr, "extensions", &from.Extensions, &to.Extensions)
translate.MergeP(tr, ts2, &r2, "fips", &from.FIPS, &to.FIPS)
translate.MergeP2(tr, ts2, &r2, "kernel_arguments", &from.KernelArguments, "kernelArguments", &to.KernelArguments)
translate.MergeP2(tr, ts2, &r2, "kernel_type", &from.KernelType, "kernelType", &to.KernelType)
ts.MergeP2("openshift", "spec", ts2)
r.Merge(r2)

// apply FIPS options to LUKS volumes
ts.Merge(addLuksFipsOptions(&mc))

// finally, check the fully desugared config for RHCOS and MCO support
r.Merge(validateRHCOSSupport(mc, ts))
r.Merge(validateMCOSupport(mc, ts))

return mc, ts, r
}

// ToMachineConfig4_13 translates the config to a MachineConfig. It returns a
// report of any errors or warnings in the source and resultant config. If
// the report has fatal errors or it encounters other problems translating,
// an error is returned.
func (c Config) ToMachineConfig4_13(options common.TranslateOptions) (result.MachineConfig, report.Report, error) {
cfg, r, err := cutil.Translate(c, "ToMachineConfig4_13Unvalidated", options)
return cfg.(result.MachineConfig), r, err
}

// ToIgn3_2Unvalidated translates the config to an Ignition config. It also
// returns the set of translations it did so paths in the resultant config
// can be tracked back to their source in the source config. No config
// validation is performed on input or output.
func (c Config) ToIgn3_2Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) {
mc, ts, r := c.ToMachineConfig4_13Unvalidated(options)
cfg := mc.Spec.Config

// report warnings if there are any non-empty fields in Spec (other
// than the Ignition config itself) that we're ignoring
mc.Spec.Config = types.Config{}
warnings := translate.PrefixReport(cutil.CheckForElidedFields(mc.Spec), "spec")
// translate from json space into yaml space
r.Merge(cutil.TranslateReportPaths(warnings, ts))

ts = ts.Descend(path.New("json", "spec", "config"))
return cfg, ts, r
}

// ToIgn3_2 translates the config to an Ignition config. It returns a
// report of any errors or warnings in the source and resultant config. If
// the report has fatal errors or it encounters other problems translating,
// an error is returned.
func (c Config) ToIgn3_2(options common.TranslateOptions) (types.Config, report.Report, error) {
cfg, r, err := cutil.Translate(c, "ToIgn3_2Unvalidated", options)
return cfg.(types.Config), r, err
}

// ToConfigBytes translates from a v4.13 Butane config to a v4.13 MachineConfig or a v3.2.0 Ignition config. It returns a report of any errors or
// warnings in the source and resultant config. If the report has fatal errors or it encounters other problems
// translating, an error is returned.
func ToConfigBytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) {
if options.Raw {
return cutil.TranslateBytes(input, &Config{}, "ToIgn3_2", options)
} else {
return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_13", options)
}
}

func addLuksFipsOptions(mc *result.MachineConfig) translate.TranslationSet {
ts := translate.NewTranslationSet("yaml", "json")
if !util.IsTrue(mc.Spec.FIPS) {
return ts
}

OUTER:
for i := range mc.Spec.Config.Storage.Luks {
luks := &mc.Spec.Config.Storage.Luks[i]
// Only add options if the user hasn't already specified
// a cipher option. Do this in-place, since config merging
// doesn't support conditional logic.
for _, option := range luks.Options {
if option == fipsCipherOption ||
strings.HasPrefix(string(option), string(fipsCipherOption)+"=") ||
option == fipsCipherShortOption {
continue OUTER
}
}
for j := 0; j < 2; j++ {
ts.AddTranslation(path.New("yaml", "openshift", "fips"), path.New("json", "spec", "config", "storage", "luks", i, "options", len(luks.Options)+j))
}
if len(luks.Options) == 0 {
ts.AddTranslation(path.New("yaml", "openshift", "fips"), path.New("json", "spec", "config", "storage", "luks", i, "options"))
}
luks.Options = append(luks.Options, fipsCipherOption, fipsCipherArgument)
}
return ts
}

// Error on fields that are rejected by RHCOS.
//
// Some of these fields may have been generated by sugar (e.g.
// boot_device.luks), so we work in JSON (output) space and then translate
// paths back to YAML (input) space. That's also the reason we do these
// checks after translation, rather than during validation.
func validateRHCOSSupport(mc result.MachineConfig, ts translate.TranslationSet) report.Report {
var r report.Report
for i, fs := range mc.Spec.Config.Storage.Filesystems {
if fs.Format != nil && *fs.Format == "btrfs" {
// we don't ship mkfs.btrfs
r.AddOnError(path.New("json", "spec", "config", "storage", "filesystems", i, "format"), common.ErrBtrfsSupport)
}
}
return cutil.TranslateReportPaths(r, ts)
}

// Error on fields that are rejected outright by the MCO, or that are
// unsupported by the MCO and we want to discourage.
//
// https://github.com/openshift/machine-config-operator/blob/d6dabadeca05/MachineConfigDaemon.md#supported-vs-unsupported-ignition-config-changes
//
// Some of these fields may have been generated by sugar (e.g. storage.trees),
// so we work in JSON (output) space and then translate paths back to YAML
// (input) space. That's also the reason we do these checks after
// translation, rather than during validation.
func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) report.Report {
// Error classes for the purposes of this function:
//
// UNPARSABLE - Cannot be rendered into a config by the MCC. If
// present in MC, MCC will mark the pool degraded. We reject these.
//
// FORBIDDEN - Not supported by the MCD. If present in MC, MCD will
// mark the node degraded. We reject these.
//
// IMMUTABLE - Permitted in MC, passed through to Ignition, but not
// supported by the MCD. MCD will mark the node degraded if the
// field changes after the node is provisioned. We reject these
// outright to discourage their use.
//
// TRIPWIRE - A subset of fields in the containing struct are
// supported by the MCD. If the struct contents change after the node
// is provisioned, and the struct contains unsupported fields, MCD
// will mark the node degraded, even if the change only affects
// supported fields. We reject these.

var r report.Report
for i := range mc.Spec.Config.Storage.Directories {
// IMMUTABLE
r.AddOnError(path.New("json", "spec", "config", "storage", "directories", i), common.ErrDirectorySupport)
}
for i, file := range mc.Spec.Config.Storage.Files {
if len(file.Append) > 0 {
// FORBIDDEN
r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "append"), common.ErrFileAppendSupport)
}
if file.Contents.Source != nil {
fileSource, err := url.Parse(*file.Contents.Source)
// parse errors will be caught by normal config validation
if err == nil && fileSource.Scheme != "data" {
// FORBIDDEN
r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "contents", "source"), common.ErrFileSchemeSupport)
}
}
if file.Mode != nil && *file.Mode & ^0777 != 0 {
// UNPARSABLE
r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "mode"), common.ErrFileSpecialModeSupport)
}
}
for i := range mc.Spec.Config.Storage.Links {
// IMMUTABLE
// If you change this to be less restrictive without adding
// link support in the MCO, consider what should happen if
// the user specifies a storage.tree that includes symlinks.
r.AddOnError(path.New("json", "spec", "config", "storage", "links", i), common.ErrLinkSupport)
}
for i := range mc.Spec.Config.Passwd.Groups {
// IMMUTABLE
r.AddOnError(path.New("json", "spec", "config", "passwd", "groups", i), common.ErrGroupSupport)
}
for i, user := range mc.Spec.Config.Passwd.Users {
if user.Name == "core" {
// PasswordHash and SSHAuthorizedKeys are managed; other fields are not
v := reflect.ValueOf(user)
t := v.Type()
for j := 0; j < v.NumField(); j++ {
fv := v.Field(j)
ft := t.Field(j)
switch ft.Name {
case "Name", "PasswordHash", "SSHAuthorizedKeys":
continue
default:
if fv.IsValid() && !fv.IsZero() {
tag := strings.Split(ft.Tag.Get("json"), ",")[0]
// TRIPWIRE
r.AddOnError(path.New("json", "spec", "config", "passwd", "users", i, tag), common.ErrUserFieldSupport)
}
}
}
} else {
// TRIPWIRE
r.AddOnError(path.New("json", "spec", "config", "passwd", "users", i), common.ErrUserNameSupport)
}
}
return cutil.TranslateReportPaths(r, ts)
}
Loading