github.com/openshift/installer@v1.4.17/pkg/asset/agent/mirror/registriesconf.go (about) 1 package mirror 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "regexp" 9 10 "github.com/containers/image/v5/pkg/sysregistriesv2" 11 "github.com/pelletier/go-toml" 12 "github.com/pkg/errors" 13 "github.com/sirupsen/logrus" 14 15 "github.com/openshift/installer/pkg/asset" 16 "github.com/openshift/installer/pkg/asset/agent" 17 "github.com/openshift/installer/pkg/asset/agent/joiner" 18 "github.com/openshift/installer/pkg/asset/agent/workflow" 19 "github.com/openshift/installer/pkg/asset/ignition/bootstrap" 20 "github.com/openshift/installer/pkg/asset/releaseimage" 21 "github.com/openshift/installer/pkg/types" 22 ) 23 24 var ( 25 // RegistriesConfFilename defines the name of the file on disk 26 RegistriesConfFilename = filepath.Join(mirrorConfigDir, "registries.conf") 27 ) 28 29 // The default registries.conf file is the podman default as it appears in 30 // CoreOS, with no unqualified-search-registries. 31 const defaultRegistriesConf = ` 32 # NOTE: RISK OF USING UNQUALIFIED IMAGE NAMES 33 # We recommend always using fully qualified image names including the registry 34 # server (full dns name), namespace, image name, and tag 35 # (e.g., registry.redhat.io/ubi8/ubi:latest). Pulling by digest (i.e., 36 # quay.io/repository/name@digest) further eliminates the ambiguity of tags. 37 # When using short names, there is always an inherent risk that the image being 38 # pulled could be spoofed. For example, a user wants to pull an image named 39 # 'foobar' from a registry and expects it to come from myregistry.com. If 40 # myregistry.com is not first in the search list, an attacker could place a 41 # different 'foobar' image at a registry earlier in the search list. The user 42 # would accidentally pull and run the attacker's image and code rather than the 43 # intended content. We recommend only adding registries which are completely 44 # trusted (i.e., registries which don't allow unknown or anonymous users to 45 # create accounts with arbitrary names). This will prevent an image from being 46 # spoofed, squatted or otherwise made insecure. If it is necessary to use one 47 # of these registries, it should be added at the end of the list. 48 # 49 # # An array of host[:port] registries to try when pulling an unqualified image, in order. 50 51 unqualified-search-registries = [] 52 53 # [[registry]] 54 # # The "prefix" field is used to choose the relevant [[registry]] TOML table; 55 # # (only) the TOML table with the longest match for the input image name 56 # # (taking into account namespace/repo/tag/digest separators) is used. 57 # # 58 # # The prefix can also be of the form: *.example.com for wildcard subdomain 59 # # matching. 60 # # 61 # # If the prefix field is missing, it defaults to be the same as the "location" field. 62 # prefix = "example.com/foo" 63 # 64 # # If true, unencrypted HTTP as well as TLS connections with untrusted 65 # # certificates are allowed. 66 # insecure = false 67 # 68 # # If true, pulling images with matching names is forbidden. 69 # blocked = false 70 # 71 # # The physical location of the "prefix"-rooted namespace. 72 # # 73 # # By default, this is equal to "prefix" (in which case "prefix" can be omitted 74 # # and the [[registry]] TOML table can only specify "location"). 75 # # 76 # # Example: Given 77 # # prefix = "example.com/foo" 78 # # location = "internal-registry-for-example.net/bar" 79 # # requests for the image example.com/foo/myimage:latest will actually work with the 80 # # internal-registry-for-example.net/bar/myimage:latest image. 81 # 82 # # The location can be empty iff prefix is in a 83 # # wildcarded format: "*.example.com". In this case, the input reference will 84 # # be used as-is without any rewrite. 85 # location = internal-registry-for-example.com/bar" 86 # 87 # # (Possibly-partial) mirrors for the "prefix"-rooted namespace. 88 # # 89 # # The mirrors are attempted in the specified order; the first one that can be 90 # # contacted and contains the image will be used (and if none of the mirrors contains the image, 91 # # the primary location specified by the "registry.location" field, or using the unmodified 92 # # user-specified reference, is tried last). 93 # # 94 # # Each TOML table in the "mirror" array can contain the following fields, with the same semantics 95 # # as if specified in the [[registry]] TOML table directly: 96 # # - location 97 # # - insecure 98 # [[registry.mirror]] 99 # location = "example-mirror-0.local/mirror-for-foo" 100 # [[registry.mirror]] 101 # location = "example-mirror-1.local/mirrors/foo" 102 # insecure = true 103 # # Given the above, a pull of example.com/foo/image:latest will try: 104 # # 1. example-mirror-0.local/mirror-for-foo/image:latest 105 # # 2. example-mirror-1.local/mirrors/foo/image:latest 106 # # 3. internal-registry-for-example.net/bar/image:latest 107 # # in order, and use the first one that exists. 108 ` 109 110 // RegistriesConf generates the registries.conf file. 111 type RegistriesConf struct { 112 File *asset.File 113 Config *sysregistriesv2.V2RegistriesConf 114 MirrorConfig []RegistriesConfig 115 } 116 117 // RegistriesConfig holds the data extracted from registries.conf 118 type RegistriesConfig struct { 119 Location string 120 Mirror string 121 } 122 123 var _ asset.WritableAsset = (*RegistriesConf)(nil) 124 125 // Name returns a human friendly name for the asset. 126 func (*RegistriesConf) Name() string { 127 return "Mirror Registries Config" 128 } 129 130 // Dependencies returns all of the dependencies directly needed to generate 131 // the asset. 132 func (*RegistriesConf) Dependencies() []asset.Asset { 133 return []asset.Asset{ 134 &workflow.AgentWorkflow{}, 135 &joiner.ClusterInfo{}, 136 &agent.OptionalInstallConfig{}, 137 &releaseimage.Image{}, 138 } 139 } 140 141 // Generate generates the registries.conf file from install-config. 142 func (i *RegistriesConf) Generate(_ context.Context, dependencies asset.Parents) error { 143 agentWorkflow := &workflow.AgentWorkflow{} 144 clusterInfo := &joiner.ClusterInfo{} 145 installConfig := &agent.OptionalInstallConfig{} 146 releaseImage := &releaseimage.Image{} 147 dependencies.Get(installConfig, releaseImage, agentWorkflow, clusterInfo) 148 149 var imageDigestSources []types.ImageDigestSource 150 var deprecatedImageContentSources []types.ImageContentSource 151 var image string 152 153 switch agentWorkflow.Workflow { 154 case workflow.AgentWorkflowTypeInstall: 155 if installConfig.Supplied { 156 imageDigestSources = installConfig.Config.ImageDigestSources 157 deprecatedImageContentSources = installConfig.Config.DeprecatedImageContentSources 158 } 159 image = releaseImage.PullSpec 160 161 case workflow.AgentWorkflowTypeAddNodes: 162 imageDigestSources = clusterInfo.ImageDigestSources 163 deprecatedImageContentSources = clusterInfo.DeprecatedImageContentSources 164 image = clusterInfo.ReleaseImage 165 166 default: 167 return fmt.Errorf("AgentWorkflowType value not supported: %s", agentWorkflow.Workflow) 168 } 169 170 if len(deprecatedImageContentSources) == 0 && len(imageDigestSources) == 0 { 171 return i.generateDefaultRegistriesConf() 172 } 173 174 err := i.generateRegistriesConf(imageDigestSources, deprecatedImageContentSources) 175 if err != nil { 176 return err 177 } 178 179 if !i.releaseImageIsSameInRegistriesConf(image) { 180 logrus.Warnf(fmt.Sprintf("The imageDigestSources configuration in install-config.yaml should have at least one source field matching the releaseImage value %s", releaseImage.PullSpec)) 181 } 182 183 registriesData, err := toml.Marshal(i.Config) 184 if err != nil { 185 return err 186 } 187 188 i.File = &asset.File{ 189 Filename: RegistriesConfFilename, 190 Data: registriesData, 191 } 192 193 return nil 194 } 195 196 func (i *RegistriesConf) generateRegistriesConf(imageDigestSources []types.ImageDigestSource, deprecatedImageContentSources []types.ImageContentSource) error { 197 if len(deprecatedImageContentSources) != 0 && len(imageDigestSources) != 0 { 198 return fmt.Errorf("invalid install-config.yaml, cannot set imageContentSources and imageDigestSources at the same time") 199 } 200 201 digestMirrorSources := []types.ImageDigestSource{} 202 if len(deprecatedImageContentSources) > 0 { 203 digestMirrorSources = bootstrap.ContentSourceToDigestMirror(deprecatedImageContentSources) 204 } else if len(imageDigestSources) > 0 { 205 digestMirrorSources = append(digestMirrorSources, imageDigestSources...) 206 } 207 208 registries := &sysregistriesv2.V2RegistriesConf{ 209 Registries: []sysregistriesv2.Registry{}, 210 } 211 for _, group := range bootstrap.MergedMirrorSets(digestMirrorSources) { 212 if len(group.Mirrors) == 0 { 213 continue 214 } 215 216 registry := sysregistriesv2.Registry{} 217 registry.Endpoint.Location = group.Source 218 registry.MirrorByDigestOnly = true 219 for _, mirror := range group.Mirrors { 220 registry.Mirrors = append(registry.Mirrors, sysregistriesv2.Endpoint{Location: mirror}) 221 } 222 registries.Registries = append(registries.Registries, registry) 223 } 224 i.Config = registries 225 i.setMirrorConfig(i.Config) 226 227 return nil 228 } 229 230 // Files returns the files generated by the asset. 231 func (i *RegistriesConf) Files() []*asset.File { 232 if i.File != nil { 233 return []*asset.File{i.File} 234 } 235 return []*asset.File{} 236 } 237 238 // Load returns RegistriesConf asset from the disk. 239 func (i *RegistriesConf) Load(f asset.FileFetcher) (bool, error) { 240 ctx := context.TODO() 241 242 releaseImage := &releaseimage.Image{} 243 if err := releaseImage.Generate(ctx, asset.Parents{}); err != nil { 244 return false, fmt.Errorf("failed to generate the release image asset: %w", err) 245 } 246 247 file, err := f.FetchByName(RegistriesConfFilename) 248 if err != nil { 249 if os.IsNotExist(err) { 250 return false, nil 251 } 252 return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", RegistriesConfFilename)) 253 } 254 255 registriesConf := &sysregistriesv2.V2RegistriesConf{} 256 if err := toml.Unmarshal(file.Data, registriesConf); err != nil { 257 return false, errors.Wrapf(err, "failed to unmarshal %s", RegistriesConfFilename) 258 } 259 260 i.File, i.Config = file, registriesConf 261 i.setMirrorConfig(i.Config) 262 263 if string(i.File.Data) != defaultRegistriesConf { 264 if i.validateRegistriesConf() { 265 if !i.releaseImageIsSameInRegistriesConf(releaseImage.PullSpec) { 266 logrus.Warnf(fmt.Sprintf("%s should have an entry matching the releaseImage %s", RegistriesConfFilename, releaseImage.PullSpec)) 267 } 268 } 269 } 270 271 return true, nil 272 } 273 274 func (i *RegistriesConf) validateRegistriesConf() bool { 275 for _, registry := range i.Config.Registries { 276 if registry.Endpoint.Location == "" { 277 logrus.Warnf(fmt.Sprintf("Location key not found in %s", RegistriesConfFilename)) 278 return false 279 } 280 } 281 return true 282 } 283 284 func (i *RegistriesConf) releaseImageIsSameInRegistriesConf(releaseImage string) bool { 285 return GetMirrorFromRelease(releaseImage, i) != "" 286 } 287 288 func (i *RegistriesConf) generateDefaultRegistriesConf() error { 289 i.File = &asset.File{ 290 Filename: RegistriesConfFilename, 291 Data: []byte(defaultRegistriesConf), 292 } 293 registriesConf := &sysregistriesv2.V2RegistriesConf{} 294 if err := toml.Unmarshal([]byte(defaultRegistriesConf), registriesConf); err != nil { 295 return errors.Wrapf(err, "failed to unmarshal %s", RegistriesConfFilename) 296 } 297 i.Config = registriesConf 298 return nil 299 } 300 301 func (i *RegistriesConf) setMirrorConfig(registriesConf *sysregistriesv2.V2RegistriesConf) { 302 mirrorConfig := make([]RegistriesConfig, len(registriesConf.Registries)) 303 for i, reg := range registriesConf.Registries { 304 mirrorConfig[i] = RegistriesConfig{ 305 Location: reg.Location, 306 Mirror: reg.Mirrors[0].Location, 307 } 308 } 309 i.MirrorConfig = mirrorConfig 310 } 311 312 // GetMirrorFromRelease gets the matching mirror configured for the releaseImage. 313 func GetMirrorFromRelease(releaseImage string, registriesConfig *RegistriesConf) string { 314 source := regexp.MustCompile(`^(.+?)(@sha256)?:(.+)`).FindStringSubmatch(releaseImage) 315 for _, config := range registriesConfig.MirrorConfig { 316 if config.Location == source[1] { 317 // include the tag with the build release image 318 switch len(source) { 319 case 4: 320 // Has Sha256 321 return fmt.Sprintf("%s%s:%s", config.Mirror, source[2], source[3]) 322 case 3: 323 return fmt.Sprintf("%s:%s", config.Mirror, source[2]) 324 } 325 } 326 } 327 328 return "" 329 }