|  | 
|  | 1 | +// Copyright 2025 Red Hat, Inc. | 
|  | 2 | +// | 
|  | 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | 4 | +// you may not use this file except in compliance with the License. | 
|  | 5 | +// You may obtain a copy of the License at | 
|  | 6 | +// | 
|  | 7 | +//     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | 8 | +// | 
|  | 9 | +// Unless required by applicable law or agreed to in writing, software | 
|  | 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | 
|  | 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | 12 | +// See the License for the specific language governing permissions and | 
|  | 13 | +// limitations under the License. | 
|  | 14 | + | 
|  | 15 | +package fips | 
|  | 16 | + | 
|  | 17 | +import ( | 
|  | 18 | +	"context" | 
|  | 19 | +	"fmt" | 
|  | 20 | +	"os" | 
|  | 21 | +	"regexp" | 
|  | 22 | +	"time" | 
|  | 23 | + | 
|  | 24 | +	"github.com/pkg/errors" | 
|  | 25 | + | 
|  | 26 | +	coreosarch "github.com/coreos/stream-metadata-go/arch" | 
|  | 27 | + | 
|  | 28 | +	"github.com/coreos/coreos-assembler/mantle/kola" | 
|  | 29 | +	"github.com/coreos/coreos-assembler/mantle/kola/cluster" | 
|  | 30 | +	"github.com/coreos/coreos-assembler/mantle/kola/register" | 
|  | 31 | +	"github.com/coreos/coreos-assembler/mantle/platform" | 
|  | 32 | +	"github.com/coreos/coreos-assembler/mantle/platform/conf" | 
|  | 33 | +) | 
|  | 34 | + | 
|  | 35 | +var failConfig = conf.Ignition(`{ | 
|  | 36 | +	"ignition": { | 
|  | 37 | +		"version": "3.4.0" | 
|  | 38 | +	}, | 
|  | 39 | +	"storage": { | 
|  | 40 | +		"filesystems": [ | 
|  | 41 | +			{ | 
|  | 42 | +				"device": "/dev/mapper/root", | 
|  | 43 | +				"format": "xfs", | 
|  | 44 | +				"label": "root", | 
|  | 45 | +				"wipeFilesystem": true | 
|  | 46 | +			} | 
|  | 47 | +		], | 
|  | 48 | +		"luks": [ | 
|  | 49 | +			{ | 
|  | 50 | +				"clevis": { | 
|  | 51 | +					"tpm2": true | 
|  | 52 | +				}, | 
|  | 53 | +				"device": "/dev/disk/by-partlabel/root", | 
|  | 54 | +				"label": "luks-root", | 
|  | 55 | +				"name": "root", | 
|  | 56 | +				"options": [ | 
|  | 57 | +					"--cipher", | 
|  | 58 | +					"aes-cbc-essiv:sha256", | 
|  | 59 | +					"--pbkdf", | 
|  | 60 | +					"argon2i" | 
|  | 61 | +				], | 
|  | 62 | +				"wipeVolume": true | 
|  | 63 | +			} | 
|  | 64 | +		], | 
|  | 65 | +		"files": [ | 
|  | 66 | +			{ | 
|  | 67 | +				"group": { | 
|  | 68 | +					"name": "root" | 
|  | 69 | +				}, | 
|  | 70 | +				"overwrite": true, | 
|  | 71 | +				"path": "/etc/ignition-machine-config-encapsulated.json", | 
|  | 72 | +				"user": { | 
|  | 73 | +					"name": "root" | 
|  | 74 | +				}, | 
|  | 75 | +				"contents": { | 
|  | 76 | +					"source": "data:,%7B%22metadata%22%3A%7B%22name%22%3A%22rendered-worker-1cc576110e0cf8396831ce4016f63900%22%2C%22selfLink%22%3A%22%2Fapis%2Fmachineconfiguration.openshift.io%2Fv1%2Fmachineconfigs%2Frendered-worker-1cc576110e0cf8396831ce4016f63900%22%2C%22uid%22%3A%2248871c03-899d-4332-a5f5-bef94e54b23f%22%2C%22resourceVersion%22%3A%224168%22%2C%22generation%22%3A1%2C%22creationTimestamp%22%3A%222019-11-04T15%3A54%3A08Z%22%2C%22annotations%22%3A%7B%22machineconfiguration.openshift.io%2Fgenerated-by-controller-version%22%3A%22bd846958bc95d049547164046a962054fca093df%22%7D%2C%22ownerReferences%22%3A%5B%7B%22apiVersion%22%3A%22machineconfiguration.openshift.io%2Fv1%22%2C%22kind%22%3A%22MachineConfigPool%22%2C%22name%22%3A%22worker%22%2C%22uid%22%3A%223d0dee9e-c9d6-4656-a4a9-81785b9ab01a%22%2C%22controller%22%3Atrue%2C%22blockOwnerDeletion%22%3Atrue%7D%5D%7D%2C%22spec%22%3A%7B%22osImageURL%22%3A%22registry.svc.ci.openshift.org%2Focp%2F4.3-2019-11-04-125204%40sha256%3A8a344c5b157bd01c3ca1abfcef0004fc39f5d69cac1cdaad0fd8dd332ad8e272%22%2C%22config%22%3A%7B%22ignition%22%3A%7B%22config%22%3A%7B%7D%2C%22security%22%3A%7B%22tls%22%3A%7B%7D%7D%2C%22timeouts%22%3A%7B%7D%2C%22version%22%3A%223.0.0%22%7D%2C%22networkd%22%3A%7B%7D%2C%22passwd%22%3A%7B%7D%2C%22storage%22%3A%7B%7D%2C%22systemd%22%3A%7B%7D%7D%2C%22kernelArguments%22%3A%5B%5D%2C%22fips%22%3Atrue%7D%7D", | 
|  | 77 | +					"verification": {} | 
|  | 78 | +				}, | 
|  | 79 | +				"mode": 420 | 
|  | 80 | +			} | 
|  | 81 | +		] | 
|  | 82 | +	} | 
|  | 83 | +}`) | 
|  | 84 | + | 
|  | 85 | +func init() { | 
|  | 86 | +	register.RegisterTest(®ister.Test{ | 
|  | 87 | +		Name:        "fips.failure", | 
|  | 88 | +		Description: "Verify cryptsetup lukscreate will fail with FIPS and incompatible crypto algorithms.", | 
|  | 89 | +		Run:         runFipsFailure, | 
|  | 90 | +		ClusterSize: 0, | 
|  | 91 | +		Platforms:   []string{"qemu"}, | 
|  | 92 | +		Tags:        []string{"ignition"}, | 
|  | 93 | +		Distros:     []string{"rhcos"}, | 
|  | 94 | +	}) | 
|  | 95 | +} | 
|  | 96 | + | 
|  | 97 | +func runFipsFailure(c cluster.TestCluster) { | 
|  | 98 | +	if err := ignitionFailure(c); err != nil { | 
|  | 99 | +		c.Fatal(err.Error()) | 
|  | 100 | +	} | 
|  | 101 | +} | 
|  | 102 | + | 
|  | 103 | +// Read file and verify if it contains a pattern | 
|  | 104 | +// 1. Read file, make sure it exists | 
|  | 105 | +// 2. regex for pattern | 
|  | 106 | +func fileContainsPattern(path string, searchPattern string) (bool, error) { | 
|  | 107 | +	file, err := os.ReadFile(path) | 
|  | 108 | +	if err != nil { | 
|  | 109 | +		return false, err | 
|  | 110 | +	} | 
|  | 111 | +	// File has content, but the pattern is not present | 
|  | 112 | +	match := regexp.MustCompile(searchPattern).Match(file) | 
|  | 113 | +	if match { | 
|  | 114 | +		// Pattern found | 
|  | 115 | +		return true, nil | 
|  | 116 | +	} | 
|  | 117 | +	// Pattern not found | 
|  | 118 | +	return false, nil | 
|  | 119 | +} | 
|  | 120 | + | 
|  | 121 | +// Start the VM, take string and grep for it in the temporary console logs | 
|  | 122 | +func verifyError(builder *platform.QemuBuilder, searchPattern string) error { | 
|  | 123 | +	inst, err := builder.Exec() | 
|  | 124 | +	if err != nil { | 
|  | 125 | +		return err | 
|  | 126 | +	} | 
|  | 127 | +	defer inst.Destroy() | 
|  | 128 | +	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) | 
|  | 129 | + | 
|  | 130 | +	defer cancel() | 
|  | 131 | + | 
|  | 132 | +	errchan := make(chan error) | 
|  | 133 | +	go func() { | 
|  | 134 | +		resultingError := inst.WaitAll(ctx) | 
|  | 135 | +		if resultingError == nil { | 
|  | 136 | +			resultingError = fmt.Errorf("ignition unexpectedly succeeded") | 
|  | 137 | +		} else if resultingError == platform.ErrInitramfsEmergency { | 
|  | 138 | +			// Expected initramfs failure, checking the console file to ensure | 
|  | 139 | +			// that it failed the expected way | 
|  | 140 | +			found, err := fileContainsPattern(builder.ConsoleFile, searchPattern) | 
|  | 141 | +			if err != nil { | 
|  | 142 | +				resultingError = errors.Wrapf(err, "looking for pattern '%s' in file '%s' failed", searchPattern, builder.ConsoleFile) | 
|  | 143 | +			} else if !found { | 
|  | 144 | +				resultingError = fmt.Errorf("pattern '%s' in file '%s' not found", searchPattern, builder.ConsoleFile) | 
|  | 145 | +			} else { | 
|  | 146 | +				// The expected case | 
|  | 147 | +				resultingError = nil | 
|  | 148 | +			} | 
|  | 149 | +		} else { | 
|  | 150 | +			resultingError = errors.Wrapf(resultingError, "expected initramfs emergency.target error") | 
|  | 151 | +		} | 
|  | 152 | +		errchan <- resultingError | 
|  | 153 | +	}() | 
|  | 154 | + | 
|  | 155 | +	select { | 
|  | 156 | +	case <-ctx.Done(): | 
|  | 157 | +		if err := inst.Kill(); err != nil { | 
|  | 158 | +			return errors.Wrapf(err, "failed to kill the vm instance") | 
|  | 159 | +		} | 
|  | 160 | +		return errors.Wrapf(ctx.Err(), "timed out waiting for initramfs error") | 
|  | 161 | +	case err := <-errchan: | 
|  | 162 | +		if err != nil { | 
|  | 163 | +			return err | 
|  | 164 | +		} | 
|  | 165 | +		return nil | 
|  | 166 | +	} | 
|  | 167 | +} | 
|  | 168 | + | 
|  | 169 | +func ignitionFailure(c cluster.TestCluster) error { | 
|  | 170 | +	builder := platform.NewQemuBuilder() | 
|  | 171 | +	defer builder.Close() | 
|  | 172 | + | 
|  | 173 | +	// Prepare Ingnition config | 
|  | 174 | +	failConfig, err := failConfig.Render(conf.FailWarnings) | 
|  | 175 | +	if err != nil { | 
|  | 176 | +		return errors.Wrapf(err, "creating invalid FIPS config") | 
|  | 177 | +	} | 
|  | 178 | + | 
|  | 179 | +	// Create a temporary log file | 
|  | 180 | +	consoleFile := c.H.TempFile("console-") | 
|  | 181 | + | 
|  | 182 | +	// Instruct builder to use it | 
|  | 183 | +	builder.ConsoleFile = consoleFile.Name() | 
|  | 184 | +	builder.SetConfig(failConfig) | 
|  | 185 | +	err = builder.AddBootDisk(&platform.Disk{ | 
|  | 186 | +		BackingFile: kola.QEMUOptions.DiskImage, | 
|  | 187 | +	}) | 
|  | 188 | +	if err != nil { | 
|  | 189 | +		return err | 
|  | 190 | +	} | 
|  | 191 | + | 
|  | 192 | +	builder.MemoryMiB = 4096 | 
|  | 193 | +	switch coreosarch.CurrentRpmArch() { | 
|  | 194 | +	case "ppc64le": | 
|  | 195 | +		builder.MemoryMiB = 8192 | 
|  | 196 | +	} | 
|  | 197 | +	builder.Firmware = kola.QEMUOptions.Firmware | 
|  | 198 | + | 
|  | 199 | +	searchPattern := "Only PBKDF2 is supported in FIPS mode" | 
|  | 200 | +	if err := verifyError(builder, searchPattern); err != nil { | 
|  | 201 | +		return err | 
|  | 202 | +	} | 
|  | 203 | +	return nil | 
|  | 204 | +} | 
0 commit comments