@@ -18,6 +18,7 @@ import (
18
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
19
"k8s.io/apimachinery/pkg/runtime"
20
20
"k8s.io/apimachinery/pkg/util/intstr"
21
+ "k8s.io/apimachinery/pkg/util/sets"
21
22
"k8s.io/apimachinery/pkg/util/validation"
22
23
"k8s.io/client-go/dynamic"
23
24
"k8s.io/kubernetes/pkg/api/legacyscheme"
84
85
privkeyName = "router.pem"
85
86
privkeyPath = secretsPath + "/" + privkeyName
86
87
88
+ defaultMutualTLSAuth = "none"
89
+ clientCertConfigDir = "/etc/pki/tls/client-certs"
90
+ clientCertConfigCA = "ca.pem"
91
+ clientCertConfigCRL = "crl.pem"
92
+
87
93
defaultCertificatePath = path .Join (defaultCertificateDir , "tls.crt" )
88
94
)
89
95
@@ -237,6 +243,23 @@ type RouterConfig struct {
237
243
Threads int32
238
244
239
245
Local bool
246
+
247
+ // MutualTLSAuth controls access to the router using a mutually agreed
248
+ // upon TLS authentication mechanism (example client certificates).
249
+ // One of: required | optional | none - the default is none.
250
+ MutualTLSAuth string
251
+
252
+ // MutualTLSAuthCA contains the CA certificates that will be used
253
+ // to verify a client's certificate.
254
+ MutualTLSAuthCA string
255
+
256
+ // MutualTLSAuthCRL contains the certificate revocation list used to
257
+ // verify a client's certificate.
258
+ MutualTLSAuthCRL string
259
+
260
+ // MutualTLSAuthFilter contains the value to filter requests based on
261
+ // a client certificate subject field substring match.
262
+ MutualTLSAuthFilter string
240
263
}
241
264
242
265
const (
@@ -271,6 +294,8 @@ func NewCmdRouter(f kcmdutil.Factory, parentName, name string, out, errout io.Wr
271
294
StatsPort : defaultStatsPort ,
272
295
HostNetwork : true ,
273
296
HostPorts : true ,
297
+
298
+ MutualTLSAuth : defaultMutualTLSAuth ,
274
299
}
275
300
276
301
cmd := & cobra.Command {
@@ -325,15 +350,25 @@ func NewCmdRouter(f kcmdutil.Factory, parentName, name string, out, errout io.Wr
325
350
cmd .Flags ().BoolVar (& cfg .Local , "local" , cfg .Local , "If true, do not contact the apiserver" )
326
351
cmd .Flags ().Int32Var (& cfg .Threads , "threads" , cfg .Threads , "Specifies the number of threads for the haproxy router." )
327
352
353
+ cmd .Flags ().StringVar (& cfg .MutualTLSAuth , "mutual-tls-auth" , cfg .MutualTLSAuth , "Controls access to the router using mutually agreed upon TLS configuration (example client certificates). You can choose one of 'required', 'optional', or 'none'. The default is none." )
354
+ cmd .Flags ().StringVar (& cfg .MutualTLSAuthCA , "mutual-tls-auth-ca" , cfg .MutualTLSAuthCA , "Optional path to a file containing one or more CA certificates used for mutual TLS authentication. The CA certificate[s] are used by the router to verify a client's certificate." )
355
+ cmd .Flags ().StringVar (& cfg .MutualTLSAuthCRL , "mutual-tls-auth-crl" , cfg .MutualTLSAuthCRL , "Optional path to a file containing the certificate revocation list used for mutual TLS authentication. The certificate revocation list is used by the router to verify a client's certificate." )
356
+ cmd .Flags ().StringVar (& cfg .MutualTLSAuthFilter , "mutual-tls-auth-filter" , cfg .MutualTLSAuthFilter , "Optional regular expression to filter the client certificates. If the client certificate subject field does _not_ match this regular expression, requests will be rejected by the router." )
357
+
328
358
cfg .Action .BindForOutput (cmd .Flags ())
329
359
cmd .Flags ().String ("output-version" , "" , "The preferred API versions of the output objects" )
330
360
331
361
return cmd
332
362
}
333
363
364
+ // generateMutualTLSSecretName generates a mutual TLS auth secret name.
365
+ func generateMutualTLSSecretName (prefix string ) string {
366
+ return fmt .Sprintf ("%s-mutual-tls-auth" , prefix )
367
+ }
368
+
334
369
// generateSecretsConfig generates any Secret and Volume objects, such
335
370
// as SSH private keys, that are necessary for the router container.
336
- func generateSecretsConfig (cfg * RouterConfig , namespace string , defaultCert [] byte , certName string ) ([]* kapi.Secret , []kapi.Volume , []kapi.VolumeMount , error ) {
371
+ func generateSecretsConfig (cfg * RouterConfig , namespace , certName string , defaultCert , mtlsAuthCA , mtlsAuthCRL [] byte ) ([]* kapi.Secret , []kapi.Volume , []kapi.VolumeMount , error ) {
337
372
var secrets []* kapi.Secret
338
373
var volumes []kapi.Volume
339
374
var mounts []kapi.VolumeMount
@@ -440,6 +475,42 @@ func generateSecretsConfig(cfg *RouterConfig, namespace string, defaultCert []by
440
475
}
441
476
mounts = append (mounts , mount )
442
477
478
+ mtlsSecretData := map [string ][]byte {}
479
+ if len (mtlsAuthCA ) > 0 {
480
+ mtlsSecretData [clientCertConfigCA ] = mtlsAuthCA
481
+ }
482
+ if len (mtlsAuthCRL ) > 0 {
483
+ mtlsSecretData [clientCertConfigCRL ] = mtlsAuthCRL
484
+ }
485
+
486
+ if len (mtlsSecretData ) > 0 {
487
+ secretName := generateMutualTLSSecretName (cfg .Name )
488
+ secret := & kapi.Secret {
489
+ ObjectMeta : metav1.ObjectMeta {
490
+ Name : secretName ,
491
+ },
492
+ Data : mtlsSecretData ,
493
+ }
494
+ secrets = append (secrets , secret )
495
+
496
+ volume := kapi.Volume {
497
+ Name : "mutual-tls-config" ,
498
+ VolumeSource : kapi.VolumeSource {
499
+ Secret : & kapi.SecretVolumeSource {
500
+ SecretName : secretName ,
501
+ },
502
+ },
503
+ }
504
+ volumes = append (volumes , volume )
505
+
506
+ mount := kapi.VolumeMount {
507
+ Name : volume .Name ,
508
+ ReadOnly : true ,
509
+ MountPath : clientCertConfigDir ,
510
+ }
511
+ mounts = append (mounts , mount )
512
+ }
513
+
443
514
return secrets , volumes , mounts , nil
444
515
}
445
516
@@ -608,6 +679,14 @@ func RunCmdRouter(f kcmdutil.Factory, cmd *cobra.Command, out, errout io.Writer,
608
679
if err != nil {
609
680
return fmt .Errorf ("error getting client: %v" , err )
610
681
}
682
+
683
+ if len (cfg .MutualTLSAuthCA ) > 0 || len (cfg .MutualTLSAuthCRL ) > 0 {
684
+ secretName := generateMutualTLSSecretName (cfg .Name )
685
+ if _ , err := kClient .Core ().Secrets (namespace ).Get (secretName , metav1.GetOptions {}); err == nil {
686
+ return fmt .Errorf ("router could not be created: mutual tls secret %q already exists" , secretName )
687
+ }
688
+ }
689
+
611
690
service , err := kClient .Core ().Services (namespace ).Get (name , metav1.GetOptions {})
612
691
if err != nil {
613
692
if ! generate {
@@ -660,6 +739,20 @@ func RunCmdRouter(f kcmdutil.Factory, cmd *cobra.Command, out, errout io.Writer,
660
739
return fmt .Errorf ("router could not be created; error reading default certificate file: %v" , err )
661
740
}
662
741
742
+ mtlsAuthOptions := []string {"required" , "optional" , "none" }
743
+ allowedMutualTLSAuthOptions := sets .NewString (mtlsAuthOptions ... )
744
+ if ! allowedMutualTLSAuthOptions .Has (cfg .MutualTLSAuth ) {
745
+ return fmt .Errorf ("invalid mutual tls auth option %v, expected one of %v" , cfg .MutualTLSAuth , mtlsAuthOptions )
746
+ }
747
+ mtlsAuthCA , err := fileutil .LoadData (cfg .MutualTLSAuthCA )
748
+ if err != nil {
749
+ return fmt .Errorf ("reading ca certificates for mutual tls auth: %v" , err )
750
+ }
751
+ mtlsAuthCRL , err := fileutil .LoadData (cfg .MutualTLSAuthCRL )
752
+ if err != nil {
753
+ return fmt .Errorf ("reading certificate revocation list for mutual tls auth: %v" , err )
754
+ }
755
+
663
756
if len (cfg .StatsPassword ) == 0 {
664
757
cfg .StatsPassword = generateStatsPassword ()
665
758
if ! cfg .Action .ShouldPrint () {
@@ -719,6 +812,20 @@ func RunCmdRouter(f kcmdutil.Factory, cmd *cobra.Command, out, errout io.Writer,
719
812
env ["ROUTER_METRICS_TLS_CERT_FILE" ] = "/etc/pki/tls/metrics/tls.crt"
720
813
env ["ROUTER_METRICS_TLS_KEY_FILE" ] = "/etc/pki/tls/metrics/tls.key"
721
814
}
815
+ mtlsAuth := strings .TrimSpace (cfg .MutualTLSAuth )
816
+ if len (mtlsAuth ) > 0 && mtlsAuth != defaultMutualTLSAuth {
817
+ env ["ROUTER_MUTUAL_TLS_AUTH" ] = cfg .MutualTLSAuth
818
+ if len (mtlsAuthCA ) > 0 {
819
+ env ["ROUTER_MUTUAL_TLS_AUTH_CA" ] = path .Join (clientCertConfigDir , clientCertConfigCA )
820
+ }
821
+ if len (mtlsAuthCRL ) > 0 {
822
+ env ["ROUTER_MUTUAL_TLS_AUTH_CRL" ] = path .Join (clientCertConfigDir , clientCertConfigCRL )
823
+ }
824
+ if len (cfg .MutualTLSAuthFilter ) > 0 {
825
+ env ["ROUTER_MUTUAL_TLS_AUTH_FILTER" ] = strings .Replace (cfg .MutualTLSAuthFilter , " " , "\\ " , - 1 )
826
+ }
827
+ }
828
+
722
829
env .Add (secretEnv )
723
830
if len (defaultCert ) > 0 {
724
831
if cfg .SecretsAsEnv {
@@ -729,7 +836,7 @@ func RunCmdRouter(f kcmdutil.Factory, cmd *cobra.Command, out, errout io.Writer,
729
836
}
730
837
env .Add (app.Environment {"DEFAULT_CERTIFICATE_DIR" : defaultCertificateDir })
731
838
var certName = fmt .Sprintf ("%s-certs" , cfg .Name )
732
- secrets , volumes , routerMounts , err := generateSecretsConfig (cfg , namespace , defaultCert , certName )
839
+ secrets , volumes , routerMounts , err := generateSecretsConfig (cfg , namespace , certName , defaultCert , mtlsAuthCA , mtlsAuthCRL )
733
840
if err != nil {
734
841
return fmt .Errorf ("router could not be created: %v" , err )
735
842
}
0 commit comments