Skip to content

Commit 7787d67

Browse files
committed
*: implement additional pull registries
Signed-off-by: Antonio Murdaca <[email protected]>
1 parent fa6e2d8 commit 7787d67

File tree

12 files changed

+309
-41
lines changed

12 files changed

+309
-41
lines changed

cmd/crio/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ image_volumes = "{{ .ImageVolumes }}"
131131
insecure_registries = [
132132
{{ range $opt := .InsecureRegistries }}{{ printf "\t%q,\n" $opt }}{{ end }}]
133133
134+
# registries is used to specify a comma separated list of registries to be used
135+
# when pulling an unqualified image (e.g. fedora:rawhide).
136+
registries = [
137+
{{ range $opt := .Registries }}{{ printf "\t%q,\n" $opt }}{{ end }}]
138+
134139
# The "crio.network" table contains settings pertaining to the
135140
# management of CNI plugins.
136141
[crio.network]

cmd/crio/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ func mergeConfig(config *server.Config, ctx *cli.Context) error {
7878
if ctx.GlobalIsSet("insecure-registry") {
7979
config.InsecureRegistries = ctx.GlobalStringSlice("insecure-registry")
8080
}
81+
if ctx.GlobalIsSet("registry") {
82+
config.Registries = ctx.GlobalStringSlice("registry")
83+
}
8184
if ctx.GlobalIsSet("default-transport") {
8285
config.DefaultTransport = ctx.GlobalString("default-transport")
8386
}
@@ -220,6 +223,10 @@ func main() {
220223
Name: "insecure-registry",
221224
Usage: "whether to disable TLS verification for the given registry",
222225
},
226+
cli.StringSliceFlag{
227+
Name: "registry",
228+
Usage: "registry to be prepended when pulling unqualified images, can be specified multiple times",
229+
},
223230
cli.StringFlag{
224231
Name: "default-transport",
225232
Usage: "default transport",

docs/crio.8.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ crio - OCI Kubernetes Container Runtime daemon
1616
[**--debug**]
1717
[**--default-transport**=[*value*]]
1818
[**--help**|**-h**]
19+
[**--insecure-registry**=[*value*]]
1920
[**--listen**=[*value*]]
2021
[**--log**=[*value*]]
2122
[**--log-format value**]
2223
[**--pause-command**=[*value*]]
2324
[**--pause-image**=[*value*]]
25+
[**--registry**=[*value*]]
2426
[**--root**=[*value*]]
2527
[**--runroot**=[*value*]]
2628
[**--runtime**=[*value*]]
@@ -73,6 +75,20 @@ set the CPU profile file path
7375
**--help, -h**
7476
Print usage statement
7577

78+
**--insecure-registry=**
79+
Enable insecure registry communication, i.e., enable un-encrypted
80+
and/or untrusted communication.
81+
82+
List of insecure registries can contain an element with CIDR notation
83+
to specify a whole subnet. Insecure registries accept HTTP and/or
84+
accept HTTPS with certificates from unknown CAs.
85+
86+
Enabling --insecure-registry is useful when running a local registry.
87+
However, because its use creates security vulnerabilities it should
88+
ONLY be enabled for testing purposes. For increased security, users
89+
should add their CA to their system's list of trusted CAs instead of
90+
using --insecure-registry.
91+
7692
**--image-volumes**=""
7793
Image volume handling ('mkdir' or 'ignore') (default: "mkdir")
7894

@@ -97,6 +113,9 @@ set the CPU profile file path
97113
**--root**=""
98114
CRIO root dir (default: "/var/lib/containers/storage")
99115

116+
**--registry**=""
117+
Registry host which will be prepended to unqualified images, can be specified multiple times
118+
100119
**--runroot**=""
101120
CRIO state dir (default: "/var/run/containers/storage")
102121

docs/crio.conf.5.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,33 @@ The `crio` table supports the following options:
7777
**default_transport**
7878
A prefix to prepend to image names that can't be pulled as-is (default: "docker://")
7979

80-
**--image_volumes**=""
80+
**image_volumes**=""
8181
Image volume handling ('mkdir' or 'ignore') (default: "mkdir")
8282

83+
**insecure_registries**=""
84+
Enable insecure registry communication, i.e., enable un-encrypted
85+
and/or untrusted communication.
86+
87+
List of insecure registries can contain an element with CIDR notation
88+
to specify a whole subnet. Insecure registries accept HTTP and/or
89+
accept HTTPS with certificates from unknown CAs.
90+
91+
Enabling --insecure-registry is useful when running a local registry.
92+
However, because its use creates security vulnerabilities it should
93+
ONLY be enabled for testing purposes. For increased security, users
94+
should add their CA to their system's list of trusted CAs instead of
95+
using --insecure-registry.
96+
8397
**pause_command**=""
8498
Path to the pause executable in the pause image (default: "/pause")
8599

86100
**pause_image**=""
87101
Image which contains the pause executable (default: "kubernetes/pause")
88102

103+
**registries**=""
104+
Comma separated list of registries that will be prepended when pulling
105+
unqualified images
106+
89107
## CRIO.NETWORK TABLE
90108

91109
**network_dir**=""

libkpod/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ type ImageConfig struct {
154154
InsecureRegistries []string `toml:"insecure_registries"`
155155
// ImageVolumes controls how volumes specified in image config are handled
156156
ImageVolumes ImageVolumesType `toml:"image_volumes"`
157+
// Registries holds a list of registries used to pull unqualified images
158+
Registries []string `toml:"registries"`
157159
}
158160

159161
// NetworkConfig represents the "crio.network" TOML config table

libkpod/container_server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func New(config *Config) (*ContainerServer, error) {
8989
return nil, err
9090
}
9191

92-
imageService, err := storage.GetImageService(store, config.DefaultTransport, config.InsecureRegistries)
92+
imageService, err := storage.GetImageService(store, config.DefaultTransport, config.InsecureRegistries, config.Registries)
9393
if err != nil {
9494
return nil, err
9595
}

pkg/storage/image.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package storage
22

33
import (
4+
"errors"
45
"net"
6+
"path/filepath"
7+
"strings"
58

69
"github.com/containers/image/copy"
710
"github.com/containers/image/docker/reference"
@@ -31,6 +34,7 @@ type imageService struct {
3134
defaultTransport string
3235
insecureRegistryCIDRs []*net.IPNet
3336
indexConfigs map[string]*indexInfo
37+
registries []string
3438
}
3539

3640
// ImageServer wraps up various CRI-related activities into a reusable
@@ -50,6 +54,9 @@ type ImageServer interface {
5054
GetStore() storage.Store
5155
// CanPull preliminary checks whether we're allowed to pull an image
5256
CanPull(imageName string, options *copy.Options) (bool, error)
57+
// ResolveNames takes an image reference and if it's unqualified (w/o hostname),
58+
// it uses crio's default registries to qualify it.
59+
ResolveNames(imageName string) ([]string, error)
5360
}
5461

5562
func (svc *imageService) ListImages(filter string) ([]ImageResult, error) {
@@ -271,11 +278,43 @@ func (svc *imageService) isSecureIndex(indexName string) bool {
271278
return true
272279
}
273280

281+
func isValidHostname(hostname string) bool {
282+
return hostname != "" && !strings.Contains(hostname, "/") &&
283+
(strings.Contains(hostname, ".") ||
284+
strings.Contains(hostname, ":") || hostname == "localhost")
285+
}
286+
287+
func (svc *imageService) ResolveNames(imageName string) ([]string, error) {
288+
domain, rest := splitDomain(imageName)
289+
if len(domain) != 0 && isValidHostname(domain) {
290+
// this means the image is already fully qualified
291+
return []string{imageName}, nil
292+
}
293+
// we got an unqualified image here, we can't go ahead w/o registries configured
294+
// properly.
295+
if len(svc.registries) == 0 {
296+
return nil, errors.New("no registries configured while trying to pull an unqualified image")
297+
}
298+
// this means we got an image in the form of "busybox"
299+
// we need to use additional registries...
300+
// normalize the unqualified image to be domain/repo/image...
301+
images := []string{}
302+
for _, r := range svc.registries {
303+
path := rest
304+
if !isValidHostname(domain) {
305+
// This is the case where we have an image like "runcom/busybox"
306+
path = imageName
307+
}
308+
images = append(images, filepath.Join(r, path))
309+
}
310+
return images, nil
311+
}
312+
274313
// GetImageService returns an ImageServer that uses the passed-in store, and
275314
// which will prepend the passed-in defaultTransport value to an image name if
276315
// a name that's passed to its PullImage() method can't be resolved to an image
277316
// in the store and can't be resolved to a source on its own.
278-
func GetImageService(store storage.Store, defaultTransport string, insecureRegistries []string) (ImageServer, error) {
317+
func GetImageService(store storage.Store, defaultTransport string, insecureRegistries []string, registries []string) (ImageServer, error) {
279318
if store == nil {
280319
var err error
281320
store, err = storage.GetStore(storage.DefaultStoreOptions)
@@ -284,11 +323,22 @@ func GetImageService(store storage.Store, defaultTransport string, insecureRegis
284323
}
285324
}
286325

326+
seenRegistries := make(map[string]bool, len(registries))
327+
cleanRegistries := []string{}
328+
for _, r := range registries {
329+
if seenRegistries[r] {
330+
continue
331+
}
332+
cleanRegistries = append(cleanRegistries, r)
333+
seenRegistries[r] = true
334+
}
335+
287336
is := &imageService{
288337
store: store,
289338
defaultTransport: defaultTransport,
290339
indexConfigs: make(map[string]*indexInfo, 0),
291340
insecureRegistryCIDRs: make([]*net.IPNet, 0),
341+
registries: cleanRegistries,
292342
}
293343

294344
insecureRegistries = append(insecureRegistries, "127.0.0.0/8")

pkg/storage/image_regexp.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package storage
2+
3+
// This is a fork of docker/distribution code to be used when manipulating image
4+
// references.
5+
// DO NOT EDIT THIS FILE.
6+
7+
import "regexp"
8+
9+
var (
10+
// alphaNumericRegexp defines the alpha numeric atom, typically a
11+
// component of names. This only allows lower case characters and digits.
12+
alphaNumericRegexp = match(`[a-z0-9]+`)
13+
14+
// separatorRegexp defines the separators allowed to be embedded in name
15+
// components. This allow one period, one or two underscore and multiple
16+
// dashes.
17+
separatorRegexp = match(`(?:[._]|__|[-]*)`)
18+
19+
// nameComponentRegexp restricts registry path component names to start
20+
// with at least one letter or number, with following parts able to be
21+
// separated by one period, one or two underscore and multiple dashes.
22+
nameComponentRegexp = expression(
23+
alphaNumericRegexp,
24+
optional(repeated(separatorRegexp, alphaNumericRegexp)))
25+
26+
// domainComponentRegexp restricts the registry domain component of a
27+
// repository name to start with a component as defined by domainRegexp
28+
// and followed by an optional port.
29+
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
30+
31+
// domainRegexp defines the structure of potential domain components
32+
// that may be part of image names. This is purposely a subset of what is
33+
// allowed by DNS to ensure backwards compatibility with Docker image
34+
// names.
35+
domainRegexp = expression(
36+
domainComponentRegexp,
37+
optional(repeated(literal(`.`), domainComponentRegexp)),
38+
optional(literal(`:`), match(`[0-9]+`)))
39+
40+
// NameRegexp is the format for the name component of references. The
41+
// regexp has capturing groups for the domain and name part omitting
42+
// the separating forward slash from either.
43+
NameRegexp = expression(
44+
optional(domainRegexp, literal(`/`)),
45+
nameComponentRegexp,
46+
optional(repeated(literal(`/`), nameComponentRegexp)))
47+
48+
// anchoredNameRegexp is used to parse a name value, capturing the
49+
// domain and trailing components.
50+
anchoredNameRegexp = anchored(
51+
optional(capture(domainRegexp), literal(`/`)),
52+
capture(nameComponentRegexp,
53+
optional(repeated(literal(`/`), nameComponentRegexp))))
54+
55+
// IdentifierRegexp is the format for string identifier used as a
56+
// content addressable identifier using sha256. These identifiers
57+
// are like digests without the algorithm, since sha256 is used.
58+
IdentifierRegexp = match(`([a-f0-9]{64})`)
59+
60+
// ShortIdentifierRegexp is the format used to represent a prefix
61+
// of an identifier. A prefix may be used to match a sha256 identifier
62+
// within a list of trusted identifiers.
63+
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
64+
)
65+
66+
// match compiles the string to a regular expression.
67+
var match = regexp.MustCompile
68+
69+
// literal compiles s into a literal regular expression, escaping any regexp
70+
// reserved characters.
71+
func literal(s string) *regexp.Regexp {
72+
re := match(regexp.QuoteMeta(s))
73+
74+
if _, complete := re.LiteralPrefix(); !complete {
75+
panic("must be a literal")
76+
}
77+
78+
return re
79+
}
80+
81+
func splitDomain(name string) (string, string) {
82+
match := anchoredNameRegexp.FindStringSubmatch(name)
83+
if len(match) != 3 {
84+
return "", name
85+
}
86+
return match[1], match[2]
87+
}
88+
89+
// expression defines a full expression, where each regular expression must
90+
// follow the previous.
91+
func expression(res ...*regexp.Regexp) *regexp.Regexp {
92+
var s string
93+
for _, re := range res {
94+
s += re.String()
95+
}
96+
97+
return match(s)
98+
}
99+
100+
// optional wraps the expression in a non-capturing group and makes the
101+
// production optional.
102+
func optional(res ...*regexp.Regexp) *regexp.Regexp {
103+
return match(group(expression(res...)).String() + `?`)
104+
}
105+
106+
// repeated wraps the regexp in a non-capturing group to get one or more
107+
// matches.
108+
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
109+
return match(group(expression(res...)).String() + `+`)
110+
}
111+
112+
// group wraps the regexp in a non-capturing group.
113+
func group(res ...*regexp.Regexp) *regexp.Regexp {
114+
return match(`(?:` + expression(res...).String() + `)`)
115+
}
116+
117+
// capture wraps the expression in a capturing group.
118+
func capture(res ...*regexp.Regexp) *regexp.Regexp {
119+
return match(`(` + expression(res...).String() + `)`)
120+
}
121+
122+
// anchored anchors the regular expression by adding start and end delimiters.
123+
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
124+
return match(`^` + expression(res...).String() + `$`)
125+
}

0 commit comments

Comments
 (0)