Skip to content

Commit 39664e5

Browse files
author
OpenShift Bot
authored
Merge pull request #14114 from miminar/secure-images-prune-by-default
Merged by openshift-bot
2 parents 5a19120 + 50b0858 commit 39664e5

File tree

10 files changed

+180
-25
lines changed

10 files changed

+180
-25
lines changed

contrib/completions/bash/oadm

+2
Original file line numberDiff line numberDiff line change
@@ -4546,6 +4546,8 @@ _oadm_prune_images()
45464546
local_nonpersistent_flags+=("--certificate-authority=")
45474547
flags+=("--confirm")
45484548
local_nonpersistent_flags+=("--confirm")
4549+
flags+=("--force-insecure")
4550+
local_nonpersistent_flags+=("--force-insecure")
45494551
flags+=("--keep-tag-revisions=")
45504552
local_nonpersistent_flags+=("--keep-tag-revisions=")
45514553
flags+=("--keep-younger-than=")

contrib/completions/bash/oc

+2
Original file line numberDiff line numberDiff line change
@@ -4555,6 +4555,8 @@ _oc_adm_prune_images()
45554555
local_nonpersistent_flags+=("--certificate-authority=")
45564556
flags+=("--confirm")
45574557
local_nonpersistent_flags+=("--confirm")
4558+
flags+=("--force-insecure")
4559+
local_nonpersistent_flags+=("--force-insecure")
45584560
flags+=("--keep-tag-revisions=")
45594561
local_nonpersistent_flags+=("--keep-tag-revisions=")
45604562
flags+=("--keep-younger-than=")

contrib/completions/bash/openshift

+4
Original file line numberDiff line numberDiff line change
@@ -4546,6 +4546,8 @@ _openshift_admin_prune_images()
45464546
local_nonpersistent_flags+=("--certificate-authority=")
45474547
flags+=("--confirm")
45484548
local_nonpersistent_flags+=("--confirm")
4549+
flags+=("--force-insecure")
4550+
local_nonpersistent_flags+=("--force-insecure")
45494551
flags+=("--keep-tag-revisions=")
45504552
local_nonpersistent_flags+=("--keep-tag-revisions=")
45514553
flags+=("--keep-younger-than=")
@@ -9669,6 +9671,8 @@ _openshift_cli_adm_prune_images()
96699671
local_nonpersistent_flags+=("--certificate-authority=")
96709672
flags+=("--confirm")
96719673
local_nonpersistent_flags+=("--confirm")
9674+
flags+=("--force-insecure")
9675+
local_nonpersistent_flags+=("--force-insecure")
96729676
flags+=("--keep-tag-revisions=")
96739677
local_nonpersistent_flags+=("--keep-tag-revisions=")
96749678
flags+=("--keep-younger-than=")

contrib/completions/zsh/oadm

+2
Original file line numberDiff line numberDiff line change
@@ -4695,6 +4695,8 @@ _oadm_prune_images()
46954695
local_nonpersistent_flags+=("--certificate-authority=")
46964696
flags+=("--confirm")
46974697
local_nonpersistent_flags+=("--confirm")
4698+
flags+=("--force-insecure")
4699+
local_nonpersistent_flags+=("--force-insecure")
46984700
flags+=("--keep-tag-revisions=")
46994701
local_nonpersistent_flags+=("--keep-tag-revisions=")
47004702
flags+=("--keep-younger-than=")

contrib/completions/zsh/oc

+2
Original file line numberDiff line numberDiff line change
@@ -4704,6 +4704,8 @@ _oc_adm_prune_images()
47044704
local_nonpersistent_flags+=("--certificate-authority=")
47054705
flags+=("--confirm")
47064706
local_nonpersistent_flags+=("--confirm")
4707+
flags+=("--force-insecure")
4708+
local_nonpersistent_flags+=("--force-insecure")
47074709
flags+=("--keep-tag-revisions=")
47084710
local_nonpersistent_flags+=("--keep-tag-revisions=")
47094711
flags+=("--keep-younger-than=")

contrib/completions/zsh/openshift

+4
Original file line numberDiff line numberDiff line change
@@ -4695,6 +4695,8 @@ _openshift_admin_prune_images()
46954695
local_nonpersistent_flags+=("--certificate-authority=")
46964696
flags+=("--confirm")
46974697
local_nonpersistent_flags+=("--confirm")
4698+
flags+=("--force-insecure")
4699+
local_nonpersistent_flags+=("--force-insecure")
46984700
flags+=("--keep-tag-revisions=")
46994701
local_nonpersistent_flags+=("--keep-tag-revisions=")
47004702
flags+=("--keep-younger-than=")
@@ -9818,6 +9820,8 @@ _openshift_cli_adm_prune_images()
98189820
local_nonpersistent_flags+=("--certificate-authority=")
98199821
flags+=("--confirm")
98209822
local_nonpersistent_flags+=("--confirm")
9823+
flags+=("--force-insecure")
9824+
local_nonpersistent_flags+=("--force-insecure")
98219825
flags+=("--keep-tag-revisions=")
98229826
local_nonpersistent_flags+=("--keep-tag-revisions=")
98239827
flags+=("--keep-younger-than=")

pkg/cmd/admin/prune/images.go

+62-17
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
imageapi "github.com/openshift/origin/pkg/image/api"
2929
"github.com/openshift/origin/pkg/image/prune"
3030
oserrors "github.com/openshift/origin/pkg/util/errors"
31+
"github.com/openshift/origin/pkg/util/netutils"
3132
)
3233

3334
// PruneImagesRecommendedName is the recommended command name
@@ -46,7 +47,17 @@ var (
4647
--confirm flag is needed for changes to be effective.
4748
4849
Only a user with a cluster role %s or higher who is logged-in will be able to actually
49-
delete the images.`)
50+
delete the images.
51+
52+
If the registry is secured with a certificate signed by a self-signed root certificate
53+
authority other than the one present in current user's config, you may need to specify it
54+
using --certificate-authority flag.
55+
56+
Insecure connection is allowed in following cases unless certificate-authority is specified:
57+
1. --force-insecure is given
58+
2. user's config allows for insecure connection (the user logged in to the cluster with
59+
--insecure-skip-tls-verify or allowed for insecure connection)
60+
3. registry url is not given or it's a private/link-local address`)
5061

5162
imagesExample = templates.Examples(`
5263
# See, what the prune command would delete if only images more than an hour old and obsoleted
@@ -80,11 +91,13 @@ type PruneImagesOptions struct {
8091
CABundle string
8192
RegistryUrlOverride string
8293
Namespace string
94+
ForceInsecure bool
8395

8496
OSClient client.Interface
8597
KClient kclientset.Interface
8698
RegistryClient *http.Client
8799
Out io.Writer
100+
Insecure bool
88101
}
89102

90103
// NewCmdPruneImages implements the OpenShift cli prune images command.
@@ -117,8 +130,9 @@ func NewCmdPruneImages(f *clientcmd.Factory, parentName, name string, out io.Wri
117130
cmd.Flags().DurationVar(opts.KeepYoungerThan, "keep-younger-than", *opts.KeepYoungerThan, "Specify the minimum age of an image for it to be considered a candidate for pruning.")
118131
cmd.Flags().IntVar(opts.KeepTagRevisions, "keep-tag-revisions", *opts.KeepTagRevisions, "Specify the number of image revisions for a tag in an image stream that will be preserved.")
119132
cmd.Flags().BoolVar(opts.PruneOverSizeLimit, "prune-over-size-limit", *opts.PruneOverSizeLimit, "Specify if images which are exceeding LimitRanges (see 'openshift.io/Image'), specified in the same namespace, should be considered for pruning. This flag cannot be combined with --keep-younger-than nor --keep-tag-revisions.")
120-
cmd.Flags().StringVar(&opts.CABundle, "certificate-authority", opts.CABundle, "The path to a certificate authority bundle to use when communicating with the managed Docker registries. Defaults to the certificate authority data from the current user's config file.")
133+
cmd.Flags().StringVar(&opts.CABundle, "certificate-authority", opts.CABundle, "The path to a certificate authority bundle to use when communicating with the managed Docker registries. Defaults to the certificate authority data from the current user's config file. It cannot be used together with --force-insecure.")
121134
cmd.Flags().StringVar(&opts.RegistryUrlOverride, "registry-url", opts.RegistryUrlOverride, "The address to use when contacting the registry, instead of using the default value. This is useful if you can't resolve or reach the registry (e.g.; the default is a cluster-internal URL) but you do have an alternative route that works.")
135+
cmd.Flags().BoolVar(&opts.ForceInsecure, "force-insecure", opts.ForceInsecure, "If true, allow an insecure connection to the docker registry that is hosted via HTTP or has an invalid HTTPS certificate. Whenever possible, use --certificate-authority instead of this dangerous option.")
122136

123137
return cmd
124138
}
@@ -151,7 +165,16 @@ func (o *PruneImagesOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command,
151165
}
152166
o.Out = out
153167

154-
osClient, kClient, registryClient, err := getClients(f, o.CABundle)
168+
clientConfig, err := f.ClientConfig()
169+
if err != nil {
170+
return err
171+
}
172+
173+
o.Insecure = o.ForceInsecure
174+
if !o.Insecure && len(o.CABundle) == 0 {
175+
o.Insecure = clientConfig.TLSClientConfig.Insecure || len(o.RegistryUrlOverride) == 0 || netutils.IsPrivateAddress(o.RegistryUrlOverride)
176+
}
177+
osClient, kClient, registryClient, err := getClients(f, o.CABundle, o.Insecure)
155178
if err != nil {
156179
return err
157180
}
@@ -176,6 +199,9 @@ func (o PruneImagesOptions) Validate() error {
176199
if _, err := url.Parse(o.RegistryUrlOverride); err != nil {
177200
return fmt.Errorf("invalid --registry-url flag: %v", err)
178201
}
202+
if o.ForceInsecure && len(o.CABundle) > 0 {
203+
return fmt.Errorf("--certificate-authority cannot be specified with --force-insecure")
204+
}
179205
return nil
180206
}
181207

@@ -251,6 +277,7 @@ func (o PruneImagesOptions) Run() error {
251277
DryRun: o.Confirm == false,
252278
RegistryClient: o.RegistryClient,
253279
RegistryURL: o.RegistryUrlOverride,
280+
Insecure: o.Insecure,
254281
}
255282
if o.Namespace != metav1.NamespaceAll {
256283
options.Namespace = o.Namespace
@@ -435,8 +462,10 @@ func (p *describingManifestDeleter) DeleteManifest(registryClient *http.Client,
435462
return err
436463
}
437464

438-
// getClients returns a Kube client, OpenShift client, and registry client.
439-
func getClients(f *clientcmd.Factory, caBundle string) (*client.Client, kclientset.Interface, *http.Client, error) {
465+
// getClients returns a Kube client, OpenShift client, and registry client. Note that
466+
// registryCABundle and registryInsecure=true are mutually exclusive. If registryInsecure=true is
467+
// specified, the ca bundle is ignored.
468+
func getClients(f *clientcmd.Factory, registryCABundle string, registryInsecure bool) (*client.Client, kclientset.Interface, *http.Client, error) {
440469
clientConfig, err := f.ClientConfig()
441470
if err != nil {
442471
return nil, nil, nil, err
@@ -461,8 +490,18 @@ func getClients(f *clientcmd.Factory, caBundle string) (*client.Client, kclients
461490
return nil, nil, nil, err
462491
}
463492

493+
cadata := []byte{}
494+
registryCABundleIncluded := false
495+
if len(registryCABundle) > 0 {
496+
cadata, err = ioutil.ReadFile(registryCABundle)
497+
if err != nil {
498+
return nil, nil, nil, fmt.Errorf("failed to read registry ca bundle: %v", err)
499+
}
500+
}
501+
464502
// copy the config
465503
registryClientConfig := *clientConfig
504+
registryClientConfig.TLSClientConfig.Insecure = registryInsecure
466505

467506
// zero out everything we don't want to use
468507
registryClientConfig.BearerToken = ""
@@ -471,8 +510,20 @@ func getClients(f *clientcmd.Factory, caBundle string) (*client.Client, kclients
471510
registryClientConfig.KeyFile = ""
472511
registryClientConfig.KeyData = []byte{}
473512

474-
// we have to set a username to something for the Docker login
475-
// but it's not actually used
513+
if registryInsecure {
514+
// it's not allowed to specify insecure flag together with CAs
515+
registryClientConfig.CAFile = ""
516+
registryClientConfig.CAData = []byte{}
517+
518+
} else if len(cadata) > 0 && len(registryClientConfig.CAData) == 0 {
519+
// If given, we want to append cabundle to the resulting tlsConfig.RootCAs. However, if we
520+
// leave CAData unset, tlsConfig may not be created. We could append the caBundle to the
521+
// CAData here directly if we were ok doing a binary magic, which is not the case.
522+
registryClientConfig.CAData = cadata
523+
registryCABundleIncluded = true
524+
}
525+
526+
// we have to set a username to something for the Docker login but it's not actually used
476527
registryClientConfig.Username = "unused"
477528

478529
// set the "password" to be the token
@@ -483,19 +534,13 @@ func getClients(f *clientcmd.Factory, caBundle string) (*client.Client, kclients
483534
return nil, nil, nil, err
484535
}
485536

486-
// if the user specified a CA on the command line, add it to the
487-
// client config's CA roots
488-
if tlsConfig != nil && len(caBundle) > 0 {
489-
data, err := ioutil.ReadFile(caBundle)
490-
if err != nil {
491-
return nil, nil, nil, err
492-
}
493-
537+
// Add the CA bundle to the client config's CA roots if provided and we haven't done that already.
538+
// FIXME: handle registryCABundle on one place
539+
if tlsConfig != nil && len(cadata) > 0 && !registryCABundleIncluded && !registryInsecure {
494540
if tlsConfig.RootCAs == nil {
495541
tlsConfig.RootCAs = x509.NewCertPool()
496542
}
497-
498-
tlsConfig.RootCAs.AppendCertsFromPEM(data)
543+
tlsConfig.RootCAs.AppendCertsFromPEM(cadata)
499544
}
500545

501546
transport := knet.SetTransportDefaults(&http.Transport{

pkg/image/prune/prune.go

+21-8
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
deploygraph "github.com/openshift/origin/pkg/deploy/graph/nodes"
3030
imageapi "github.com/openshift/origin/pkg/image/api"
3131
imagegraph "github.com/openshift/origin/pkg/image/graph/nodes"
32+
"github.com/openshift/origin/pkg/util/netutils"
3233
)
3334

3435
// TODO these edges should probably have an `Add***Edges` method in images/graph and be moved there
@@ -140,6 +141,8 @@ type PrunerOptions struct {
140141
RegistryClient *http.Client
141142
// RegistryURL is the URL for the registry.
142143
RegistryURL string
144+
// Allow a fallback to insecure transport when contacting the registry.
145+
Insecure bool
143146
}
144147

145148
// Pruner knows how to prune istags, images, layers and image configs.
@@ -170,7 +173,8 @@ type registryPinger interface {
170173

171174
// defaultRegistryPinger implements registryPinger.
172175
type defaultRegistryPinger struct {
173-
client *http.Client
176+
client *http.Client
177+
insecure bool
174178
}
175179

176180
func (drp *defaultRegistryPinger) ping(registry string) error {
@@ -183,23 +187,29 @@ func (drp *defaultRegistryPinger) ping(registry string) error {
183187
defer healthResponse.Body.Close()
184188

185189
if healthResponse.StatusCode != http.StatusOK {
186-
return fmt.Errorf("unexpected status code %d", healthResponse.StatusCode)
190+
return fmt.Errorf("unexpected status: %s", healthResponse.Status)
187191
}
188192

189193
return nil
190194
}
191195

192-
var err error
193-
for _, proto := range []string{"https", "http"} {
196+
var errs []error
197+
protos := make([]string, 0, 2)
198+
protos = append(protos, "https")
199+
if drp.insecure || netutils.IsPrivateAddress(registry) {
200+
protos = append(protos, "http")
201+
}
202+
for _, proto := range protos {
194203
glog.V(4).Infof("Trying %s for %s", proto, registry)
195-
err = healthCheck(proto, registry)
204+
err := healthCheck(proto, registry)
196205
if err == nil {
197-
break
206+
return nil
198207
}
208+
errs = append(errs, err)
199209
glog.V(4).Infof("Error with %s for %s: %v", proto, registry, err)
200210
}
201211

202-
return err
212+
return kerrors.NewAggregate(errs)
203213
}
204214

205215
// dryRunRegistryPinger implements registryPinger.
@@ -287,7 +297,10 @@ func NewPruner(options PrunerOptions) Pruner {
287297
if options.DryRun {
288298
rp = &dryRunRegistryPinger{}
289299
} else {
290-
rp = &defaultRegistryPinger{options.RegistryClient}
300+
rp = &defaultRegistryPinger{
301+
client: options.RegistryClient,
302+
insecure: options.Insecure,
303+
}
291304
}
292305

293306
return &pruner{

pkg/util/netutils/common.go

+34
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010
kerrors "k8s.io/apimachinery/pkg/util/errors"
1111
)
1212

13+
var localHosts []string = []string{"127.0.0.1", "::1", "localhost"}
14+
var localSubnets []string = []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7", "fe80::/10"}
15+
1316
func IPToUint32(ip net.IP) uint32 {
1417
return binary.BigEndian.Uint32(ip.To4())
1518
}
@@ -114,3 +117,34 @@ func ParseCIDRMask(cidr string) (*net.IPNet, error) {
114117
}
115118
return net, nil
116119
}
120+
121+
// IsPrivateAddress returns true if given address in format "<host>[:<port>]" is a localhost or an ip from
122+
// private network range (e.g. 172.30.0.1, 192.168.0.1).
123+
func IsPrivateAddress(addr string) bool {
124+
host, _, err := net.SplitHostPort(addr)
125+
if err != nil {
126+
// assume indexName is of the form `host` without the port and go on.
127+
host = addr
128+
}
129+
for _, localHost := range localHosts {
130+
if host == localHost {
131+
return true
132+
}
133+
}
134+
135+
ip := net.ParseIP(host)
136+
if ip == nil {
137+
return false
138+
}
139+
140+
for _, subnet := range localSubnets {
141+
ipnet, err := ParseCIDRMask(subnet)
142+
if err != nil {
143+
continue // should not happen
144+
}
145+
if ipnet.Contains(ip) {
146+
return true
147+
}
148+
}
149+
return false
150+
}

0 commit comments

Comments
 (0)