Skip to content

Commit af5c331

Browse files
committed
OCPBUGS-86794: Support disableSigstoreRegistries list for mirroring
Replace disableSigstoreForAdditionalImages with an explicit registry host list, so certified operator bundles from registry.connect.redhat.com can be mirrored without failing due to sigstore attachments. Write per-host registries.d snippets before oc-mirror runs and skip hosts that already have configuration (e.g. registry.redhat.io). Example appliance-config.yaml: ``` disableSigstoreRegistries: - registry.connect.redhat.com operators: - catalog: registry.redhat.io/redhat/redhat-operator-index:v4.19 packages: - name: gpu-operator-certified channels: - name: v26.3 ```
1 parent d700819 commit af5c331

6 files changed

Lines changed: 162 additions & 45 deletions

File tree

docs/appliance-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
| enableDefaultSources | false | Yes | bool | Enable all default CatalogSources (on openshift-marketplace namespace). Should be disabled for disconnected environments. |
2424
| enableFips | false | Yes | bool | Enable FIPS mode for the cluster. Note: 'fips' should be enabled also in install-config.yaml. |
2525
| enableInteractiveFlow | false | Yes | bool | Enable the interactive installation flow. Should be enabled to provide cluster configuration through the web UI (i.e. instead of using a config-image). |
26-
| disableSigstoreForAdditionalImages | false | Yes | bool | Disable sigstore attachments for all image domains listed under `additionalImages` (useful when source registries do not publish `.sig` attachment manifests). **Note:** this is a workaround for images that do not publish OCI `.sig` manifests (failing oc-mirror v2). |
26+
| disableSigstoreRegistries | | Yes | array | Registry hosts for which oc-mirror should not fetch sigstore `.sig` attachments (workaround when registries do not publish OCI `.sig` manifests). Example: `registry.connect.redhat.com` for certified operator bundles. Hosts that already have `/etc/containers/registries.d/<host>.yaml` in the build environment are skipped. |
2727
| additionalImages | | Yes | array | Additional images to be included in the appliance disk image. |
2828
| blockedImages | | Yes | array | Images to avoid including in the appliance disk image (by name or regular expression). |
2929
| operators | | Yes | array | Operators to be included in the appliance disk image. See examples in https://github.com/openshift/oc-mirror/blob/main/docs/imageset-config-ref.yaml. |

docs/user-guide.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,12 @@ enableInteractiveFlow: enable-interactive-flow
160160
# Default: false
161161
# [Optional]
162162
useDefaultSourceNames: use-default-source-names
163-
# Disable sigstore attachments for all registries referenced in additionalImages.
164-
# This can be required for disconnected mirroring when signature attachment manifests
165-
# are unavailable in source registries.
166-
# Note: this is a workaround for images that do not publish OCI .sig manifests (failing oc-mirror v2).
167-
# Default: false
163+
# Disable sigstore attachments for the listed registry hosts during oc-mirror.
164+
# Required when source registries do not publish OCI .sig attachment manifests (failing oc-mirror v2).
165+
# Example: certified operator bundles on registry.connect.redhat.com.
168166
# [Optional]
169-
disableSigstoreForAdditionalImages: disable-sigstore-for-additional-images
167+
# disableSigstoreRegistries:
168+
# - registry.connect.redhat.com
170169
# Additional images to be included in the appliance disk image.
171170
# [Optional]
172171
additionalImages:
@@ -292,6 +291,19 @@ Note: for each operator, ensure the name and channel are correct by listing the
292291
oc-mirror list operators --catalog=registry.redhat.io/redhat/redhat-operator-index:v4.14
293292
```
294293

294+
Certified operators that pull bundle images from `registry.connect.redhat.com` may require disabling sigstore attachments during mirroring:
295+
296+
```yaml
297+
disableSigstoreRegistries:
298+
- registry.connect.redhat.com
299+
operators:
300+
- catalog: registry.redhat.io/redhat/redhat-operator-index:v4.19
301+
packages:
302+
- name: gpu-operator-certified
303+
channels:
304+
- name: v26.3
305+
```
306+
295307
#### Install operators in cluster
296308

297309
To automatically install the included operators during cluster installation, add the relevant custom manifests to `${APPLIANCE_ASSETS}/openshift`.

pkg/asset/config/appliance_config.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,13 @@ pullSecret: pull-secret
202202
# [Optional]
203203
# useDefaultSourceNames: %t
204204
205-
# Disable sigstore attachments for all registries referenced in additionalImages.
206-
# This can be required for disconnected mirroring when signature attachment manifests
207-
# are unavailable in source registries.
208-
# Note: this is a workaround for images that do not publish OCI .sig manifests (failing oc-mirror v2).
209-
# Default: false
205+
# Disable sigstore attachments for the listed registry hosts during oc-mirror.
206+
# Required when source registries do not publish OCI .sig attachment manifests (failing oc-mirror v2).
207+
# Example: certified operator bundles on registry.connect.redhat.com.
208+
# Registries that already have configuration under /etc/containers/registries.d/ are skipped.
210209
# [Optional]
211-
# disableSigstoreForAdditionalImages: false
210+
# disableSigstoreRegistries:
211+
# - registry.connect.redhat.com
212212
213213
# Additional images to be included in the appliance disk image.
214214
# [Optional]

pkg/release/sigstore_registries.go

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
package release
22

33
import (
4+
"os"
5+
"path/filepath"
46
"sort"
57
"strings"
68

7-
"github.com/go-openapi/swag"
8-
"github.com/openshift/appliance/pkg/types"
99
)
1010

11-
const (
12-
disableSigstoreFilePath = "/etc/containers/registries.d/00-appliance-disable-sigstore.yaml"
13-
registriesDDir = "/etc/containers/registries.d"
14-
)
11+
const registriesDDir = "/etc/containers/registries.d"
12+
13+
func isValidRegistryHost(host string) bool {
14+
return strings.Contains(host, ".") || host == "localhost"
15+
}
1516

16-
func additionalImageRegistryHosts(images *[]types.Image) []string {
17-
if images == nil {
17+
func normalizeRegistryHost(entry string) string {
18+
entry = strings.TrimSpace(entry)
19+
if entry == "" {
20+
return ""
21+
}
22+
if host := imageReferenceRegistryHost(entry); host != "" {
23+
if isValidRegistryHost(host) {
24+
return host
25+
}
26+
return ""
27+
}
28+
host := strings.TrimPrefix(entry, "docker://")
29+
if strings.Contains(host, "/") {
30+
return ""
31+
}
32+
if colon := strings.LastIndex(host, ":"); colon > 0 && !strings.Contains(host, "]") {
33+
host = host[:colon]
34+
}
35+
if !isValidRegistryHost(host) {
36+
return ""
37+
}
38+
return host
39+
}
40+
41+
func disableSigstoreRegistryHosts(registries *[]string) []string {
42+
if registries == nil {
1843
return nil
1944
}
2045

2146
registryHostsMap := map[string]struct{}{}
22-
for _, img := range *images {
23-
registryHost := imageReferenceRegistryHost(img.Name)
24-
if registryHost == "" {
25-
continue
47+
for _, entry := range *registries {
48+
if host := normalizeRegistryHost(entry); host != "" {
49+
registryHostsMap[host] = struct{}{}
2650
}
27-
registryHostsMap[registryHost] = struct{}{}
2851
}
2952

3053
registryHosts := make([]string, 0, len(registryHostsMap))
@@ -52,6 +75,25 @@ func imageReferenceRegistryHost(name string) string {
5275
return ""
5376
}
5477

78+
func disableSigstoreRegistryConfigPath(host string) string {
79+
return filepath.Join(registriesDDir, host+".yaml")
80+
}
81+
82+
func hostHasRegistriesDConfig(registriesDir, host string, stat func(string) (os.FileInfo, error)) bool {
83+
_, err := stat(filepath.Join(registriesDir, host+".yaml"))
84+
return err == nil
85+
}
86+
87+
func filterHostsWithoutExistingRegistriesDConfig(hosts []string, registriesDir string, stat func(string) (os.FileInfo, error)) []string {
88+
filtered := make([]string, 0, len(hosts))
89+
for _, host := range hosts {
90+
if !hostHasRegistriesDConfig(registriesDir, host, stat) {
91+
filtered = append(filtered, host)
92+
}
93+
}
94+
return filtered
95+
}
96+
5597
// buildDisableSigstoreRegistriesConfig creates a containers-registries.d snippet
5698
// that disables sigstore attachments for the given registry hosts.
5799
// This is used as a workaround for oc-mirror v2 flows where some registries do not
@@ -72,11 +114,12 @@ func buildDisableSigstoreRegistriesConfig(registryHosts []string) []byte {
72114
}
73115

74116
func (r *release) disableSigstoreForRelevantRegistries() error {
75-
if !swag.BoolValue(r.ApplianceConfig.Config.DisableSigstoreForAdditionalImages) {
117+
registryHosts := disableSigstoreRegistryHosts(r.ApplianceConfig.Config.DisableSigstoreRegistries)
118+
if len(registryHosts) == 0 {
76119
return nil
77120
}
78121

79-
registryHosts := additionalImageRegistryHosts(r.ApplianceConfig.Config.AdditionalImages)
122+
registryHosts = filterHostsWithoutExistingRegistriesDConfig(registryHosts, registriesDDir, r.OSInterface.Stat)
80123
if len(registryHosts) == 0 {
81124
return nil
82125
}
@@ -85,5 +128,12 @@ func (r *release) disableSigstoreForRelevantRegistries() error {
85128
return err
86129
}
87130

88-
return r.OSInterface.WriteFile(disableSigstoreFilePath, buildDisableSigstoreRegistriesConfig(registryHosts), 0o644)
131+
for _, host := range registryHosts {
132+
configPath := disableSigstoreRegistryConfigPath(host)
133+
if err := r.OSInterface.WriteFile(configPath, buildDisableSigstoreRegistriesConfig([]string{host}), 0o644); err != nil {
134+
return err
135+
}
136+
}
137+
138+
return nil
89139
}

pkg/release/sigstore_registries_test.go

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package release
22

33
import (
4+
"os"
5+
"path/filepath"
46
"strings"
57
"testing"
6-
7-
"github.com/openshift/appliance/pkg/types"
88
)
99

1010
func TestImageReferenceRegistryHost(t *testing.T) {
@@ -29,31 +29,57 @@ func TestImageReferenceRegistryHost(t *testing.T) {
2929
}
3030
}
3131

32-
func TestAdditionalImageRegistryHosts(t *testing.T) {
32+
func TestNormalizeRegistryHost(t *testing.T) {
33+
t.Parallel()
34+
cases := []struct {
35+
in string
36+
want string
37+
}{
38+
{"registry.connect.redhat.com", "registry.connect.redhat.com"},
39+
{" registry.connect.redhat.com ", "registry.connect.redhat.com"},
40+
{"registry.connect.redhat.com:443", "registry.connect.redhat.com"},
41+
{"registry.connect.redhat.com/partner/foo:latest", "registry.connect.redhat.com"},
42+
{"docker://quay.io/example:latest", "quay.io"},
43+
{"", ""},
44+
{"invalid/host/extra", ""},
45+
}
46+
for _, tc := range cases {
47+
t.Run(tc.in, func(t *testing.T) {
48+
t.Parallel()
49+
if got := normalizeRegistryHost(tc.in); got != tc.want {
50+
t.Fatalf("normalizeRegistryHost(%q) = %q, want %q", tc.in, got, tc.want)
51+
}
52+
})
53+
}
54+
}
55+
56+
func TestDisableSigstoreRegistryHosts(t *testing.T) {
3357
t.Parallel()
34-
images := []types.Image{
35-
{Name: "registry.connect.redhat.com/partner/foo@sha256:deadbeef"},
36-
{Name: "docker://quay.io/example/sidecar:latest"},
37-
{Name: "registry.connect.redhat.com/partner/bar:1.0"},
58+
registries := []string{
59+
"registry.connect.redhat.com",
60+
" registry.connect.redhat.com ",
61+
"quay.io",
62+
"quay.io/example:latest",
63+
"",
3864
}
39-
got := additionalImageRegistryHosts(&images)
65+
got := disableSigstoreRegistryHosts(&registries)
4066
want := []string{"quay.io", "registry.connect.redhat.com"}
4167
if len(got) != len(want) {
42-
t.Fatalf("additionalImageRegistryHosts() returned %d hosts, want %d", len(got), len(want))
68+
t.Fatalf("disableSigstoreRegistryHosts() returned %d hosts, want %d", len(got), len(want))
4369
}
4470
for i := range want {
4571
if got[i] != want[i] {
46-
t.Fatalf("additionalImageRegistryHosts() host at index %d = %q, want %q", i, got[i], want[i])
72+
t.Fatalf("disableSigstoreRegistryHosts() host at index %d = %q, want %q", i, got[i], want[i])
4773
}
4874
}
4975

50-
if hosts := additionalImageRegistryHosts(nil); len(hosts) != 0 {
51-
t.Fatalf("expected empty list for nil images, got %v", hosts)
76+
if hosts := disableSigstoreRegistryHosts(nil); len(hosts) != 0 {
77+
t.Fatalf("expected empty list for nil registries, got %v", hosts)
5278
}
5379

54-
var empty []types.Image
55-
if hosts := additionalImageRegistryHosts(&empty); len(hosts) != 0 {
56-
t.Fatalf("expected empty list for empty images, got %v", hosts)
80+
var empty []string
81+
if hosts := disableSigstoreRegistryHosts(&empty); len(hosts) != 0 {
82+
t.Fatalf("expected empty list for empty registries, got %v", hosts)
5783
}
5884
}
5985

@@ -72,3 +98,32 @@ func TestBuildDisableSigstoreRegistriesConfig(t *testing.T) {
7298
t.Fatal("expected registry.connect.redhat.com entry in generated config")
7399
}
74100
}
101+
102+
func TestFilterHostsWithoutExistingRegistriesDConfig(t *testing.T) {
103+
t.Parallel()
104+
105+
registriesDir := t.TempDir()
106+
if err := os.WriteFile(filepath.Join(registriesDir, "registry.redhat.io.yaml"), []byte("docker:\n"), 0o644); err != nil {
107+
t.Fatal(err)
108+
}
109+
110+
hosts := []string{"registry.redhat.io", "registry.connect.redhat.com", "quay.io"}
111+
got := filterHostsWithoutExistingRegistriesDConfig(hosts, registriesDir, os.Stat)
112+
want := []string{"registry.connect.redhat.com", "quay.io"}
113+
if len(got) != len(want) {
114+
t.Fatalf("filterHostsWithoutExistingRegistriesDConfig() = %v, want %v", got, want)
115+
}
116+
for i := range want {
117+
if got[i] != want[i] {
118+
t.Fatalf("host at index %d = %q, want %q", i, got[i], want[i])
119+
}
120+
}
121+
}
122+
123+
func TestDisableSigstoreRegistryConfigPath(t *testing.T) {
124+
t.Parallel()
125+
want := filepath.Join(registriesDDir, "registry.connect.redhat.com.yaml")
126+
if got := disableSigstoreRegistryConfigPath("registry.connect.redhat.com"); got != want {
127+
t.Fatalf("disableSigstoreRegistryConfigPath() = %q, want %q", got, want)
128+
}
129+
}

pkg/types/appliance_config_type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type ApplianceConfig struct {
2626
CreatePinnedImageSets *bool `json:"createPinnedImageSets"`
2727
EnableInteractiveFlow *bool `json:"enableInteractiveFlow"`
2828
UseDefaultSourceNames *bool `json:"useDefaultSourceNames"`
29-
DisableSigstoreForAdditionalImages *bool `json:"disableSigstoreForAdditionalImages,omitempty"`
29+
DisableSigstoreRegistries *[]string `json:"disableSigstoreRegistries,omitempty"`
3030
AdditionalImages *[]Image `json:"additionalImages,omitempty"`
3131
BlockedImages *[]Image `json:"blockedImages,omitempty"`
3232
Operators *[]Operator `json:"operators,omitempty"`

0 commit comments

Comments
 (0)