diff --git a/.gitignore b/.gitignore index 44d148d..2228b05 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ Thumbs.db ######## *.log /misc-dev/ + +cover* +tmp/ +docs/tls/DESIGN.md diff --git a/ca-secret.yaml b/ca-secret.yaml new file mode 100644 index 0000000..5aabd61 --- /dev/null +++ b/ca-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWpjQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HUXgKQ3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcFhZWE5vYVc1bmRHOXVNUkF3RGdZRFZRUUhEQWRUWldGMApkR3hsTVE0d0RBWURWUVFLREFWT1IwbE9XREVlTUJ3R0ExVUVDd3dWUTI5dGJYVnVhWFI1SUNZZ1FXeHNhV0Z1ClkyVnpNQjRYRFRJek1UQXdNakl6TURReU9Wb1hEVEl6TVRFd01USXpNRFF5T1Zvd1pERUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVgpCQW9NQlU1SFNVNVlNUjR3SEFZRFZRUUxEQlZEYjIxdGRXNXBkSGtnSmlCQmJHeHBZVzVqWlhNd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEc0xteW9vc2NKNXZQbmFER3FCZkozYmQxT2F0NXYKYjQwSjcrc0ROamxhUFNGRjdNZGJyN2JFOFhkRUNGOHZCYkE2MkUwTVJRYXJHMFl5aWJBOG05OFV5TVJ1R0VRTApSZkltZ2pHVWtXdkRmU0ViSkJLZ1RXay83ckJPeDVRY1lFVnlCZ2Z2SDZMWk5hampic0VFaUFjalVObkp3WkNpCjdIWjJDSVZ5NVhpUmlKWVZLbGZwN3QvYlIwTXRtWVNEMlh6L0RoZXBRMnpkQ25zNnpFK3Q5aGxINnorcThydUUKam45ZTJaUGNBeTlHc1lNVi9WSTdIK0ZyMVBaREJJNUhtb1pZeUo4MTVqcWhiQUxGeU00Z0x3V1JlSVJuek9aZApNeVV0QW8rM1Vic1ZwQ0NOTE9XSk1WeXVUUVV1U3QwOFlXNTFDeGtESlBUMDY0bFAyZXBLQ2RQcEFnTUJBQUV3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMUStGRzVxNDVSV0JFK1JwNkpwUlZWakphMFF6ZE44V1ljcWUwTDkKVlJPbG5aSkxva2JGNzJrOEdrZ0t3YzNVZ21IRzFMbmMvdzlTbFJ1Ti9uRkMySEtZbWxqWHkwaHpWaFZLbDRjRgozc1NKV0c2dzBkcXFNVmk0bDBybFVDQlk3cDBTQXA4eFdLd2ZoanE0NU1YSGlwNERRUzVTUXUrWjJiSCtTN3pzCjNxcGhDaEpkNkpGZUNnL0pGQ1VIWjZGRXk0ZitJcGl1bU01SENKakJUcHdEVUdLcHB3a2pPeWp6Ym1Vc1hEcUYKRkIvYzZ4MnluYjlROGQ4MG1oREV5V0dFWDhtbE1ycXNrblFZd2JTenkyUitudnFLOFp3NTFGbDRSUU96K1lPZAphMUlTemZKWkYwdEtqcittS0ZSODZXWm05VWNOT1JnWjQ1WjVRNWI1V1AvMVF6dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRHNMbXlvb3NjSjV2UG4KYURHcUJmSjNiZDFPYXQ1dmI0MEo3K3NETmpsYVBTRkY3TWRicjdiRThYZEVDRjh2QmJBNjJFME1SUWFyRzBZeQppYkE4bTk4VXlNUnVHRVFMUmZJbWdqR1VrV3ZEZlNFYkpCS2dUV2svN3JCT3g1UWNZRVZ5Qmdmdkg2TFpOYWpqCmJzRUVpQWNqVU5uSndaQ2k3SFoyQ0lWeTVYaVJpSllWS2xmcDd0L2JSME10bVlTRDJYei9EaGVwUTJ6ZENuczYKekUrdDlobEg2eitxOHJ1RWpuOWUyWlBjQXk5R3NZTVYvVkk3SCtGcjFQWkRCSTVIbW9aWXlKODE1anFoYkFMRgp5TTRnTHdXUmVJUm56T1pkTXlVdEFvKzNVYnNWcENDTkxPV0pNVnl1VFFVdVN0MDhZVzUxQ3hrREpQVDA2NGxQCjJlcEtDZFBwQWdNQkFBRUNnZ0VBWWV3Tm1RMkVRSkxFNVZqSjNwaUFvd3VtQ2ZFOU1DNnI1MGJWeFlzaDFFd3MKRTNYTVlqTkVML3Q5VzNPdEl5M1VsMUUvQUt0TnpIdU9hejJ6R0MzNEhBSHhqMFA0VWtRNTFjVjlFUUFLRWc4NwpQcW1DSDN4NCtzelh4Skh5MHFFSHFmTGVMMEtLbmt3bExjYXB1RnM5dW1LM0tYTmJxSEVwM0Y1RUZoTVdIaUFiClV4cWpYZXJKWXBZMXU3RVhwUHFWaSt3dFhkWDNuT092bStZMzNLemRGNTErTXBpOHVkMGpTY0p2VEtIYXpKV1oKZE40aFF4SEpEOGpMZm8zNDRNWEFYQndkWFVuN1V4NW9qU0dVemtnU1pKTnBkQVNaUU5lN2E1Qk4xalREZ29LaQp0My9kS0NFTmFsMGdUQVI1MFArYWJxeWhSOFUxbVVuT1dsWDN1ekJsa1FLQmdRRDNFS09tWm16Z293Nk5mOWp2CmpkdzJOUUQ1Q2NlWEk4cWlxRjlMNWZoVWV0b29PajA2WlZpYjBEejNWcWlqVnRITFBQV2pRQ2ZwWC9DVHdkUTkKajNHTVBDOEhIZWJkeEZqTHpRSWlDUXAvaGFlc2RGTUhHRzFBak92VDRNdkxOaklWM29JOCszZjhIWk81Q0pYQgpJdW1xcG82YWJhV094VEhDS2pYY2YxNTI0d0tCZ1FEMHVRWGNJWlJrNjdrYU5PZWhjLzFuU2QyTVJSVG1zV3JYClFjTEtQczdWVFkwWlA1T2hsU1pRVkM2MW9PUWtuZnFScGhVbnJVVDF1NXFRbzJUSGhGcmN4a3Qrd1hCenhDWDgKUXZXY1RTTmkvZ0Y2Rkx1OEsyYXlOVlBQTDFNQjJ4ZGxTL0Z0SDRLSG5RWHM1NFM5MVZHRTR5OUd6aDIwWEcwOQp4K0FiQzlPM3d3S0JnSGNuWURXbFdrY3dmSmxEbW1WV0htbEtRTkRhcFphLzNUOTdRcEtCTTdYU2xob21sRmJ3CmY3Nk52SWx4RXQzTHhseGxadlkzdjhmdXpFRUdqd3l0Zkk2c2krVzd4eGNYVmRmY1pIWHp0RXR5TXo2WnoxMHgKcTZjaEQ2OWMwQXlPYzdOV1g2dDNnQk5vVkZFOTBiT1cyZWpDY1M0TFNYaEVwRTNITzdpKytOa1BBb0dBZW9CYgo1Sk9TbXVvOG9GZTNVMlNpaHArOUhVZy9iRE9IamZWSE1zSTUreUIwN3h5YUpCcHJNVzdTYXV6OUJ5OWxqSjhjCm05M3FWUy94OFZFNVUzNTNsV2hWeGovQ3NOQ1JTek9oaXZvNktvV0g2N3FSTjJKcVorNjE0MUtITkxpZGY0R0MKZXVONURiV1dqNzVjL2tIWUtyTW1xVVRvTGE3T3FFeHpiRmFCUnMwQ2dZQUN6UlBiQTBqK0JyOWpxenFKYU56VQpPZzJ4aUFhZlNuTzBxTkREZWZNdXhZSnFMZ0llMG9HRUExb1JlcDhvTDNDeWZYd2h3U2xqMlRjeGZZTFR5MENKCmZBejF4QjY3SUF3cGlvZnI0K1ZXekF4SC9BbnlhVGNZVGdtakdKdlhtZ3l1RGU3ZGJsa3lJU1M5ZTBLc1JzeTYKQ2hPdVZoQy96bjErbm9BMkNsdzFJdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls diff --git a/client-secret.yaml b/client-secret.yaml new file mode 100644 index 0000000..e703594 --- /dev/null +++ b/client-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVIRENDQXdTZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THM4d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdOak0zV2hjTk1qUXhNREF4TWpNd05qTTNXakJrTVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekNDCkFTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSzRPY1h4YWVrNkVQbTBDZUo5Z21iRlEKbFg1THlHNlUybEZIOFBFY3UvUlVTenJRQW1Rckh5di9mMlFBb3Q5VzhGNVlpaVVlQi85TkVGY1M2ZUVEK00xbwpVMy9ubEdHS21qTlQ5amNpbnBDZ0lYVUZ5UVQvWkd3QjFVWEdZYUViMHFWOFVVUlpTS1AxWXI5SzJBTzRxWE95CkFGTjZyUkdrdzBZTEErd3FEcDQxdFBNZEZZWWcrRit4OFZ5S1hLckZScUZRTFNPa2hzQXNwT3RZcjNOMkIzUlMKVGozeXFyNUZkc2w0VklSYWZCMWlhbGhSRWdnbDZjekZZaHZmci9kNjdNcHJvVlhudUZ5cS9OUDAvbXRnWXhvRwpKRVFoQmhnOFR4eUhiZ1lsTTFXZkxQTDh0SG5WUTZZVTVzamwrcG8vRU1yR24wNiszNnRwdksyT0RmNkJ6SDhDCkF3RUFBYU9CeFRDQndqQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFkQmdOVkhRNEVGZ1FVMkdrWmcxakUKYlcyN0lJSjV5cTB3ekJXa1FzY3dnWXNHQTFVZEl3U0JnekNCZ0tGb3BHWXdaREVMTUFrR0ExVUVCaE1DVlZNeApFekFSQmdOVkJBZ01DbGRoYzJocGJtZDBiMjR4RURBT0JnTlZCQWNNQjFObFlYUjBiR1V4RGpBTUJnTlZCQW9NCkJVNUhTVTVZTVI0d0hBWURWUVFMREJWRGIyMXRkVzVwZEhrZ0ppQkJiR3hwWVc1alpYT0NGRjRpSllsZ1hWKzYKSzFyUTQvUlpxcTJNbFFNZk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQWkwais2eDMrZjJ4ZXJlQlg2OFNmWAozM0tmc0tOaGJIOXJKeU9ROXViV1RrdHdaQnVOUmFKNEptWUtFa0IxUnVnOTFVVmdMdmZzWm5VY1FTOHRQUU8rCnNEcHFEamdwdnlZbU1LSm1HVHZ0M0tmK3JpV0dtU3g3ZDZUb0R5bGpNZ2dUdmJ4dFZhQTNtak9UcGFzb0ZWTzMKcXlVU29sUCtoZzI5M2Z2Q1JaeWhNTTd0ejdEdUIzRmFjR1JHNmNoaE55N0UzaGRpd2JjVUdIQ0VGN0ZwbWNLTgpOTlhyUVMwOW9GeUVmZEd3TkFHOUtPVHZkVDNZUzFqcmo3QnROOGlMSGxQUFFNaFk3aWhTUnlVUmN1S05vSC9BCnRTQm83eFJWV3BZMTIrMEJhaERjQ1lndzJ0RjMvd2Q1ekhwb1RuZi9YZFJiMm4vUzBKbTZueE54NUdlbDhMK2EKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ3VEbkY4V25wT2hENXQKQW5pZllKbXhVSlYrUzhodWxOcFJSL0R4SEx2MFZFczYwQUprS3g4ci8zOWtBS0xmVnZCZVdJb2xIZ2YvVFJCWApFdW5oQS9qTmFGTi81NVJoaXBvelUvWTNJcDZRb0NGMUJja0UvMlJzQWRWRnhtR2hHOUtsZkZGRVdVaWo5V0svClN0Z0R1S2x6c2dCVGVxMFJwTU5HQ3dQc0tnNmVOYlR6SFJXR0lQaGZzZkZjaWx5cXhVYWhVQzBqcEliQUxLVHIKV0s5emRnZDBVazQ5OHFxK1JYYkplRlNFV253ZFltcFlVUklJSmVuTXhXSWIzNi8zZXV6S2E2RlY1N2hjcXZ6VAo5UDVyWUdNYUJpUkVJUVlZUEU4Y2gyNEdKVE5Wbnl6eS9MUjUxVU9tRk9iSTVmcWFQeERLeHA5T3Z0K3JhYnl0CmpnMytnY3gvQWdNQkFBRUNnZ0VBSXo2VWQwaEE1TjQ5WDhoMDBWejNzaUp0cXYzQWI3ZmZmejd3aUhvM2l2RjQKckVlTGZHb0k3VmxXbTlMUEtDZE1FK2Fjem9oR3VVa0xDbjZ2Y2h0aVNZR2JDdGJEUW44VTIxamdqZWlLTUNIawp0SFAvOE8yZ0VZakxmVTMrM2VjcTM4eU5EaWlBSDRja1FEVHhDY3ZlTUNtMmpERFdrN0NIeEFxZCtEZko3dm45Cnp2enY2TkkzQWhWZktwYmszV0RONmlRajdhelNxN2dPV0JnaS81d3hmcnJqYnpRbGU0YWRvRHZXUFcxbXg4TEcKbFNQekUyWUVIbHRGN2lFbTFTTW9SMFRtQ28xOVYrTUFjQTRLek4yUk05WXNGRER0b1hSMjRodm1wS0ZiazdIQwpITm01ZTd6dUdYK1NFOW1sbDBST1RuMXhTQUw1bjMvaFI5MzFINzdGeVFLQmdRRHhoWWhiNkN2Wm5Nb3krejVWCnR4VFh6dFR4UG0yOVpYaHdVTEZVYUphVUR3Rk13MlVhMld5ZGlscDMzUkZwWlFPNnZwZXl4NE55RjVBTmh6TzMKZE11VktmMTNWekR4NkxROXpRNng3R0NmekRkdDllWTc2aUwxRzBJV3NUZUZaTWduSDE2WUNVaVFlMVRhNVNPOQphWXN5eGErTkFIaU4yVDZydGNuZ0FKMjNPUUtCZ1FDNGZaR0JaZmtQRFlQaEpKM0RmSUcyM1hua1UrM29rUElSCitWcHBmSkhjVnZ4TDFYREd4Zk5tVGZpcmFJZUYvK0ZUdGJsb0Q4eEZTeU9zZGwwNTJsVlhtV0svV3hRSGl2c2IKL1I2WHJLNjRjZXdtMVFQY3dtek1Lc24vOW02UlVsdlF3VmUyVGYzYWxHYzRVWFBCVGtJZDlESTFaWlV5a2ZmUQppQXhPL0NXcGR3S0JnQzEySlNTbm54bG5HZWhld216LytUeG1Bazhtb1NGMWFDWThDaVVKU3M2enhGcmVyTGxSCkU5RFRxaFBGMlBFdHduWDBTam1zdEdGVmJoZ2R5dTVOWGNUR0VwL1VHYkp2U3Y0WEN4MFNrVjJDNHl3ZmpTYloKKzVxSGR2a3VnblRwYzROcHREU0tDczZuYUdHTG9CNlhMMHh2U1l3UStxQTR0RU0rQkxIVmE5cUJBb0dCQUk1NApXYzlsb2lvZnM4SS85cDBxSHpuS1d3RWFWMVVMNmdRN1hiaXNmQzk5OVNQUzFsNktLMmJMdThjUzErV0JMczdvClBSL0JZMnYzbExyd1JSb1NJMm1jaUFkaUhGdWUxa0JNL2p6L0c0WlFZNSt4VEdSRXVLUUtQeWd0ZEVGQktxcFIKUkowQ0taR01uUkYreFRkNGFkS2I2OUlVZWwwdElBU25xMm1yaXFJTkFvR0FKcmtDS2Rxejg5blRYd3FnbmZrWgpSbVB5Tk1sMjlidDNIT1ZsR3ZoTjJib3lBeHFaYk4xLzdaWE5OSlFKR3J0TUx0QzFXY3o1bXE0czY1QW5KSFhaCkhQWVRyWGdmdjc2SHROMG95TDcxbFBUOTZpU3RHc0tWSmx1R1MvT3I2TXh2Nm5XTEY0aG1mUURqaGpSNVgvdEsKTW1XV0J5Q2JRTU9mRm5hRjB2azJuTXM9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-client-secret + namespace: nlk +type: kubernetes.io/tls diff --git a/cmd/certificates-test-harness/doc.go b/cmd/certificates-test-harness/doc.go new file mode 100644 index 0000000..538bed9 --- /dev/null +++ b/cmd/certificates-test-harness/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package certificates_test_harness includes functionality boostrap and test the certification.Certificates implplementation. +*/ + +package main diff --git a/cmd/certificates-test-harness/main.go b/cmd/certificates-test-harness/main.go new file mode 100644 index 0000000..3be7045 --- /dev/null +++ b/cmd/certificates-test-harness/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "context" + "errors" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path/filepath" +) + +func main() { + logrus.SetLevel(logrus.DebugLevel) + err := run() + if err != nil { + logrus.Fatal(err) + } +} + +func run() error { + logrus.Info("certificates-test-harness::run") + + ctx := context.Background() + var err error + + k8sClient, err := buildKubernetesClient() + if err != nil { + return fmt.Errorf(`error building a Kubernetes client: %w`, err) + } + + certificates, err := certification.NewCertificates(ctx, k8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating certificates: %w`, err) + } + + err = certificates.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing certificates: %w`, err) + } + + go certificates.Run() + + <-ctx.Done() + return nil +} + +func buildKubernetesClient() (*kubernetes.Clientset, error) { + logrus.Debug("Watcher::buildKubernetesClient") + + var kubeconfig *string + var k8sConfig *rest.Config + + k8sConfig, err := rest.InClusterConfig() + if errors.Is(err, rest.ErrNotInCluster) { + if home := homedir.HomeDir(); home != "" { + path := filepath.Join(home, ".kube", "config") + kubeconfig = &path + + k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) + } + } else { + return nil, fmt.Errorf(`not running in a Cluster: %w`, err) + } + } else if err != nil { + return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) + } + + client, err := kubernetes.NewForConfig(k8sConfig) + if err != nil { + return nil, fmt.Errorf(`error occurred creating a client: %w`, err) + } + return client, nil +} diff --git a/cmd/configuration-test-harness/doc.go b/cmd/configuration-test-harness/doc.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/configuration-test-harness/doc.go @@ -0,0 +1 @@ +package main diff --git a/cmd/configuration-test-harness/main.go b/cmd/configuration-test-harness/main.go new file mode 100644 index 0000000..56e8b5d --- /dev/null +++ b/cmd/configuration-test-harness/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "errors" + "fmt" + configuration2 "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path/filepath" +) + +func main() { + logrus.SetLevel(logrus.DebugLevel) + err := run() + if err != nil { + logrus.Fatal(err) + } +} + +func run() error { + logrus.Info("configuration-test-harness::run") + + ctx := context.Background() + var err error + + k8sClient, err := buildKubernetesClient() + if err != nil { + return fmt.Errorf(`error building a Kubernetes client: %w`, err) + } + + configuration, err := configuration2.NewSettings(ctx, k8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating configuration: %w`, err) + } + + err = configuration.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing configuration: %w`, err) + } + + go configuration.Run() + + <-ctx.Done() + + return err +} + +func buildKubernetesClient() (*kubernetes.Clientset, error) { + logrus.Debug("Watcher::buildKubernetesClient") + + var kubeconfig *string + var k8sConfig *rest.Config + + k8sConfig, err := rest.InClusterConfig() + if errors.Is(err, rest.ErrNotInCluster) { + if home := homedir.HomeDir(); home != "" { + path := filepath.Join(home, ".kube", "config") + kubeconfig = &path + + k8sConfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + return nil, fmt.Errorf(`error occurred building the kubeconfig: %w`, err) + } + } else { + return nil, fmt.Errorf(`not running in a Cluster: %w`, err) + } + } else if err != nil { + return nil, fmt.Errorf(`error occurred getting the Cluster config: %w`, err) + } + + client, err := kubernetes.NewForConfig(k8sConfig) + if err != nil { + return nil, fmt.Errorf(`error occurred creating a client: %w`, err) + } + return client, nil +} diff --git a/cmd/nginx-loadbalancer-kubernetes/main.go b/cmd/nginx-loadbalancer-kubernetes/main.go index c5891e2..6e6a8bc 100644 --- a/cmd/nginx-loadbalancer-kubernetes/main.go +++ b/cmd/nginx-loadbalancer-kubernetes/main.go @@ -19,6 +19,7 @@ import ( ) func main() { + logrus.SetLevel(logrus.DebugLevel) err := run() if err != nil { logrus.Fatal(err) @@ -44,6 +45,8 @@ func run() error { return fmt.Errorf(`error occurred initializing settings: %w`, err) } + go settings.Run() + synchronizerWorkqueue, err := buildWorkQueue(settings.Synchronizer.WorkQueueSettings) if err != nil { return fmt.Errorf(`error occurred building a workqueue: %w`, err) @@ -71,7 +74,6 @@ func run() error { return fmt.Errorf(`error occurred initializing the watcher: %w`, err) } - go settings.Run() go handler.Run(ctx.Done()) go synchronizer.Run(ctx.Done()) diff --git a/cmd/tls-config-factory-test-harness/doc.go b/cmd/tls-config-factory-test-harness/doc.go new file mode 100644 index 0000000..06ab7d0 --- /dev/null +++ b/cmd/tls-config-factory-test-harness/doc.go @@ -0,0 +1 @@ +package main diff --git a/cmd/tls-config-factory-test-harness/main.go b/cmd/tls-config-factory-test-harness/main.go new file mode 100644 index 0000000..3e9ef48 --- /dev/null +++ b/cmd/tls-config-factory-test-harness/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" + "os" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" + ClientCertificateSecretKey = "nlk-tls-client-secret" +) + +type TlsConfiguration struct { + Description string + Settings configuration.Settings +} + +func main() { + logrus.SetLevel(logrus.DebugLevel) + + configurations := buildConfigMap() + + for name, settings := range configurations { + fmt.Print("\033[H\033[2J") + + logrus.Infof("\n\n\t*** Building TLS config for <<< %s >>>\n\n", name) + + tlsConfig, err := authentication.NewTlsConfig(&settings.Settings) + if err != nil { + panic(err) + } + + rootCaCount := 0 + certificateCount := 0 + + if tlsConfig.RootCAs != nil { + rootCaCount = len(tlsConfig.RootCAs.Subjects()) + } + + if tlsConfig.Certificates != nil { + certificateCount = len(tlsConfig.Certificates) + } + + logrus.Infof("Successfully built TLS config: \n\tDescription: %s \n\tRootCA count: %v\n\tCertificate count: %v", settings.Description, rootCaCount, certificateCount) + + bufio.NewReader(os.Stdin).ReadBytes('\n') + } + + fmt.Print("\033[H\033[2J") + logrus.Infof("\n\n\t*** All done! ***\n\n") +} + +func buildConfigMap() map[string]TlsConfiguration { + configurations := make(map[string]TlsConfiguration) + + configurations["ss-tls"] = TlsConfiguration{ + Description: "Self-signed TLS requires just a CA certificate", + Settings: ssTlsConfig(), + } + + configurations["ss-mtls"] = TlsConfiguration{ + Description: "Self-signed mTLS requires a CA certificate and a client certificate", + Settings: ssMtlsConfig(), + } + + configurations["ca-tls"] = TlsConfiguration{ + Description: "CA TLS requires no certificates", + Settings: caTlsConfig(), + } + + configurations["ca-mtls"] = TlsConfiguration{ + Description: "CA mTLS requires a client certificate", + Settings: caMtlsConfig(), + } + + return configurations +} + +func ssTlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func ssMtlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func caTlsConfig() configuration.Settings { + return configuration.Settings{ + TlsMode: "ca-tls", + } +} + +func caMtlsConfig() configuration.Settings { + certificates := make(map[string]map[string][]byte) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + return configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } +} + +func caCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 +dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu +Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV +BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P +umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks +LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk +lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN +dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm +CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh +cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD +G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl +xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x +kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 +0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= +-----END CERTIFICATE----- +` +} + +func clientCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// clientKeyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientKeyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} + +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + certification.CertificateKeyKey: []byte(keyPEM), + } +} + +func buildCaCertificateEntry(certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + } +} diff --git a/deployments/deployment/configmap.yaml b/deployments/deployment/configmap.yaml index 8889baf..91522a6 100644 --- a/deployments/deployment/configmap.yaml +++ b/deployments/deployment/configmap.yaml @@ -1,8 +1,10 @@ apiVersion: v1 kind: ConfigMap data: - nginx-hosts: - "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + nginx-hosts: "https://192.168.96.207/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" metadata: name: nlk-config - namespace: nlk + namespace: nlk \ No newline at end of file diff --git a/deployments/deployment/deployment.yaml b/deployments/deployment/deployment.yaml index 4c871c2..11fa61f 100644 --- a/deployments/deployment/deployment.yaml +++ b/deployments/deployment/deployment.yaml @@ -17,7 +17,8 @@ spec: spec: containers: - name: nginx-loadbalancer-kubernetes - image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:latest + image: ciroque/nginx-loadbalancer-kubernetes:dev-11 +# image: ghcr.io/nginxinc/nginx-loadbalancer-kubernetes:125 imagePullPolicy: Always ports: - name: http diff --git a/deployments/rbac/clusterrole.yaml b/deployments/rbac/clusterrole.yaml index 9edf9e0..c50bed8 100644 --- a/deployments/rbac/clusterrole.yaml +++ b/deployments/rbac/clusterrole.yaml @@ -6,5 +6,5 @@ metadata: rules: - apiGroups: - "" - resources: ["services", "nodes", "configmaps"] + resources: ["services", "nodes", "configmaps", "secrets"] verbs: ["get", "watch", "list"] diff --git a/docs/DEMO/COMMANDS.md b/docs/DEMO/COMMANDS.md new file mode 100644 index 0000000..5cf7125 --- /dev/null +++ b/docs/DEMO/COMMANDS.md @@ -0,0 +1,111 @@ +# Demo commands + +## Creation / Updte / Deletion of ConfigMap + +Run the configuration test harness: + +```bash +go run cmd/configuration-test-harness/main.go +``` + +Run each of the following commands in turn and watch the output of the configuration test harness. + +Create: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" +EOF +``` + +Update: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +kind: ConfigMap +metadata: + name: nlk-config + namespace: nlk +data: + nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api,http://10.1.1.6:9000/api" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" +EOF +``` + +Delete: + +```bash +kubectl delete configmap nlk-config -n nlk +``` + +## Creation / Update / Deletion of Certificate Secrets + +Run the certificates test harness: + +```bash +go run cmd/certificates-test-harness/main.go +``` + +Run each of the following commands in turn and watch the output of the certificates test harness. + +Create: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURUekNDQWpjQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjNEUUVCQ3dVQU1HUXgKQ3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcFhZWE5vYVc1bmRHOXVNUkF3RGdZRFZRUUhEQWRUWldGMApkR3hsTVE0d0RBWURWUVFLREFWT1IwbE9XREVlTUJ3R0ExVUVDd3dWUTI5dGJYVnVhWFI1SUNZZ1FXeHNhV0Z1ClkyVnpNQjRYRFRJek1UQXdNakl6TURReU9Wb1hEVEl6TVRFd01USXpNRFF5T1Zvd1pERUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVgpCQW9NQlU1SFNVNVlNUjR3SEFZRFZRUUxEQlZEYjIxdGRXNXBkSGtnSmlCQmJHeHBZVzVqWlhNd2dnRWlNQTBHCkNTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEc0xteW9vc2NKNXZQbmFER3FCZkozYmQxT2F0NXYKYjQwSjcrc0ROamxhUFNGRjdNZGJyN2JFOFhkRUNGOHZCYkE2MkUwTVJRYXJHMFl5aWJBOG05OFV5TVJ1R0VRTApSZkltZ2pHVWtXdkRmU0ViSkJLZ1RXay83ckJPeDVRY1lFVnlCZ2Z2SDZMWk5hampic0VFaUFjalVObkp3WkNpCjdIWjJDSVZ5NVhpUmlKWVZLbGZwN3QvYlIwTXRtWVNEMlh6L0RoZXBRMnpkQ25zNnpFK3Q5aGxINnorcThydUUKam45ZTJaUGNBeTlHc1lNVi9WSTdIK0ZyMVBaREJJNUhtb1pZeUo4MTVqcWhiQUxGeU00Z0x3V1JlSVJuek9aZApNeVV0QW8rM1Vic1ZwQ0NOTE9XSk1WeXVUUVV1U3QwOFlXNTFDeGtESlBUMDY0bFAyZXBLQ2RQcEFnTUJBQUV3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMUStGRzVxNDVSV0JFK1JwNkpwUlZWakphMFF6ZE44V1ljcWUwTDkKVlJPbG5aSkxva2JGNzJrOEdrZ0t3YzNVZ21IRzFMbmMvdzlTbFJ1Ti9uRkMySEtZbWxqWHkwaHpWaFZLbDRjRgozc1NKV0c2dzBkcXFNVmk0bDBybFVDQlk3cDBTQXA4eFdLd2ZoanE0NU1YSGlwNERRUzVTUXUrWjJiSCtTN3pzCjNxcGhDaEpkNkpGZUNnL0pGQ1VIWjZGRXk0ZitJcGl1bU01SENKakJUcHdEVUdLcHB3a2pPeWp6Ym1Vc1hEcUYKRkIvYzZ4MnluYjlROGQ4MG1oREV5V0dFWDhtbE1ycXNrblFZd2JTenkyUitudnFLOFp3NTFGbDRSUU96K1lPZAphMUlTemZKWkYwdEtqcittS0ZSODZXWm05VWNOT1JnWjQ1WjVRNWI1V1AvMVF6dz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRRHNMbXlvb3NjSjV2UG4KYURHcUJmSjNiZDFPYXQ1dmI0MEo3K3NETmpsYVBTRkY3TWRicjdiRThYZEVDRjh2QmJBNjJFME1SUWFyRzBZeQppYkE4bTk4VXlNUnVHRVFMUmZJbWdqR1VrV3ZEZlNFYkpCS2dUV2svN3JCT3g1UWNZRVZ5Qmdmdkg2TFpOYWpqCmJzRUVpQWNqVU5uSndaQ2k3SFoyQ0lWeTVYaVJpSllWS2xmcDd0L2JSME10bVlTRDJYei9EaGVwUTJ6ZENuczYKekUrdDlobEg2eitxOHJ1RWpuOWUyWlBjQXk5R3NZTVYvVkk3SCtGcjFQWkRCSTVIbW9aWXlKODE1anFoYkFMRgp5TTRnTHdXUmVJUm56T1pkTXlVdEFvKzNVYnNWcENDTkxPV0pNVnl1VFFVdVN0MDhZVzUxQ3hrREpQVDA2NGxQCjJlcEtDZFBwQWdNQkFBRUNnZ0VBWWV3Tm1RMkVRSkxFNVZqSjNwaUFvd3VtQ2ZFOU1DNnI1MGJWeFlzaDFFd3MKRTNYTVlqTkVML3Q5VzNPdEl5M1VsMUUvQUt0TnpIdU9hejJ6R0MzNEhBSHhqMFA0VWtRNTFjVjlFUUFLRWc4NwpQcW1DSDN4NCtzelh4Skh5MHFFSHFmTGVMMEtLbmt3bExjYXB1RnM5dW1LM0tYTmJxSEVwM0Y1RUZoTVdIaUFiClV4cWpYZXJKWXBZMXU3RVhwUHFWaSt3dFhkWDNuT092bStZMzNLemRGNTErTXBpOHVkMGpTY0p2VEtIYXpKV1oKZE40aFF4SEpEOGpMZm8zNDRNWEFYQndkWFVuN1V4NW9qU0dVemtnU1pKTnBkQVNaUU5lN2E1Qk4xalREZ29LaQp0My9kS0NFTmFsMGdUQVI1MFArYWJxeWhSOFUxbVVuT1dsWDN1ekJsa1FLQmdRRDNFS09tWm16Z293Nk5mOWp2CmpkdzJOUUQ1Q2NlWEk4cWlxRjlMNWZoVWV0b29PajA2WlZpYjBEejNWcWlqVnRITFBQV2pRQ2ZwWC9DVHdkUTkKajNHTVBDOEhIZWJkeEZqTHpRSWlDUXAvaGFlc2RGTUhHRzFBak92VDRNdkxOaklWM29JOCszZjhIWk81Q0pYQgpJdW1xcG82YWJhV094VEhDS2pYY2YxNTI0d0tCZ1FEMHVRWGNJWlJrNjdrYU5PZWhjLzFuU2QyTVJSVG1zV3JYClFjTEtQczdWVFkwWlA1T2hsU1pRVkM2MW9PUWtuZnFScGhVbnJVVDF1NXFRbzJUSGhGcmN4a3Qrd1hCenhDWDgKUXZXY1RTTmkvZ0Y2Rkx1OEsyYXlOVlBQTDFNQjJ4ZGxTL0Z0SDRLSG5RWHM1NFM5MVZHRTR5OUd6aDIwWEcwOQp4K0FiQzlPM3d3S0JnSGNuWURXbFdrY3dmSmxEbW1WV0htbEtRTkRhcFphLzNUOTdRcEtCTTdYU2xob21sRmJ3CmY3Nk52SWx4RXQzTHhseGxadlkzdjhmdXpFRUdqd3l0Zkk2c2krVzd4eGNYVmRmY1pIWHp0RXR5TXo2WnoxMHgKcTZjaEQ2OWMwQXlPYzdOV1g2dDNnQk5vVkZFOTBiT1cyZWpDY1M0TFNYaEVwRTNITzdpKytOa1BBb0dBZW9CYgo1Sk9TbXVvOG9GZTNVMlNpaHArOUhVZy9iRE9IamZWSE1zSTUreUIwN3h5YUpCcHJNVzdTYXV6OUJ5OWxqSjhjCm05M3FWUy94OFZFNVUzNTNsV2hWeGovQ3NOQ1JTek9oaXZvNktvV0g2N3FSTjJKcVorNjE0MUtITkxpZGY0R0MKZXVONURiV1dqNzVjL2tIWUtyTW1xVVRvTGE3T3FFeHpiRmFCUnMwQ2dZQUN6UlBiQTBqK0JyOWpxenFKYU56VQpPZzJ4aUFhZlNuTzBxTkREZWZNdXhZSnFMZ0llMG9HRUExb1JlcDhvTDNDeWZYd2h3U2xqMlRjeGZZTFR5MENKCmZBejF4QjY3SUF3cGlvZnI0K1ZXekF4SC9BbnlhVGNZVGdtakdKdlhtZ3l1RGU3ZGJsa3lJU1M5ZTBLc1JzeTYKQ2hPdVZoQy96bjErbm9BMkNsdzFJdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls +EOF +``` + +Update: + +```bash +cat <<'EOF' | kubectl apply -f - +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsekNDQTMrZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THRBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdPRFEyV2hjTk1qUXhNREF4TWpNd09EUTJXakI3TVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekVWCk1CTUdBMVVFQXd3TWJYbGtiMjFoYVc0dVkyOXRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUIKQ2dLQ0FRRUF2RjJSVklsVEQvQWVSQkZSNTNrb1dlZmlhQ09keVJvWnJRSm04RGZJdk5vQ2pTdkNPUEhzZ2VSSQptcmhxcEhkc2FSK2RqSjNRbzFKemljeS9YNHgxRy9SSGRNM0d3OXNuTS9jdHBvb1FSZUtFTi90T1FnVnBMaEZQClE4K3cvaDVRSGZWWFFQcFdyeGhjTkFiK1hrbzk5SVBndE81blNvazExK2pEOGhJSVlTTUEvVElwUFBPeXNKdnkKOUhKUjBEY3pqWE1VQTR0dGphYTZ0cm8xNm81WEZsdDZPM251YTFkc0VLYVRJQmhWbXI4ZGJWVFd2c1VNYTV6WgpyR0pSa25Bc01RV1lxSVBBVG1MdXlaeHlYTG1WQkp4U1FzWTZqYVllLzcreXZGdUVCUTZ4MDByZ1Z0THpzWi9NCitZSHg2VUVXRFoxYzZuZWxxdlFja1FvSzdTd3VLUUlEQVFBQm80SUJLRENDQVNRd0NRWURWUjBUQkFJd0FEQUwKQmdOVkhROEVCQU1DQmVBd1NBWURWUjBSQkVFd1A0SU1iWGxrYjIxaGFXNHVZMjl0Z2hOelpYSjJaWEl1YlhsawpiMjFoYVc0dVkyOXRnZzRxTG0xNVpHOXRZV2x1TG1OdmJZY0VDZ0FBQ29jRUNnQUFDekFUQmdOVkhTVUVEREFLCkJnZ3JCZ0VGQlFjREFUQWRCZ05WSFE0RUZnUVVkOUVieTR0TDNZSjhqSTd6RzVKR2p5TW5vVDR3Z1lzR0ExVWQKSXdTQmd6Q0JnS0ZvcEdZd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eApFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0CmRXNXBkSGtnSmlCQmJHeHBZVzVqWlhPQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFCRFY0OWtOanpCZmpPMjlNWVd1ZFlMNktzYlFrdlFDdzZ3dEF6cVJ2Smd5WEtBbXYzcwp6OFJjZHFNRTZ2bXdWZUxpS1ZDeDFSOXY1dlhBS0hMNmFSSi9QMk1QUm1Ic0pNM2MxSVF2NjAzVVYzaCtQL3JICll3QTVVRzBCMXlMaWs3N3RIdWZtRGFKdlRZMGpvcWJ0cVEwU1ByWjh6TGJQdmo3VTc1bStHWEx5VEp0UnRjdDUKSVJTblg0NlZxbWs0Q1l2dW1SaDJJTy8yUHpKNkpUbzA5VFpEOGpDbyttbHIzSXhXOHBsRDBYSkQ1YXY4cEQ2dApDZ2xnR2FyakRWSmVCQUZobkVIWStYelZDYzJVSktVblNiaDVWUVRLdno2dCtCWTlldi9LOFJGYlcxWVd3N1Q1CmQ0UzEyaC94cUpXcUJOdXFJeldkMTMvbTZaaHoraHorb0VvRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzhYWkZVaVZNUDhCNUUKRVZIbmVTaFo1K0pvSTUzSkdobXRBbWJ3TjhpODJnS05LOEk0OGV5QjVFaWF1R3FrZDJ4cEg1Mk1uZENqVW5PSgp6TDlmakhVYjlFZDB6Y2JEMnljejl5Mm1paEJGNG9RMyswNUNCV2t1RVU5RHo3RCtIbEFkOVZkQStsYXZHRncwCkJ2NWVTajMwZytDMDdtZEtpVFhYNk1QeUVnaGhJd0Q5TWlrODg3S3dtL0wwY2xIUU56T05jeFFEaTIyTnBycTIKdWpYcWpsY1dXM283ZWU1clYyd1FwcE1nR0ZXYXZ4MXRWTmEreFF4cm5ObXNZbEdTY0N3eEJaaW9nOEJPWXU3SgpuSEpjdVpVRW5GSkN4anFOcGg3L3Y3SzhXNFFGRHJIVFN1Qlcwdk94bjh6NWdmSHBRUllOblZ6cWQ2V3E5QnlSCkNncnRMQzRwQWdNQkFBRUNnZ0VBQnRtRGh6dnhKeU5pV1FqNk0xSjVaYW81Z1BPZW5CUC9aVnV3MWtFVHVIa0QKQ1JMVGRCSlVEb3NqR3NFNXNONWVOUVVVZG1zU0RiRUVzNDVHL1VOVWRONUdyNWdyWEs3bzk0cExBTmlFd1UzUgo1TFBybU1TdFJQZ1YxaytaK09aWVNudnVudFhHUzRWZ0ZrenFJMlZNRXlBQ2pxeHhXWFFHTkdJL1VkdXNXS3FICkNlOWNGMXp0UGxBWkVIRU9JNTJEcVFZK3FhUUtNSjVIT3RNS1F6SzY5MGF0VUJmbWZxUCsxYTh4Vm1VV1cyaVgKNWpuN3IzeDRJdENwSlpwTHM2SzZRS29aNU9qd25nSTc4MkxuQ3V0cC9NZmg5RmVYSlFRRVppYXMzOW44c29NcwpCN0ZKQnpKalQwVlQxR2hVWnNwSUhra1AwR3pBQTd2Y3MwNitDQWc5Z1FLQmdRRG1CYTQ3MnlLYmwvZUIyY3BFCjdZeXVmaHk1M093OTFzNkkwYnQxYWZyajNvcHExV1VVWUJiOWhIUm1CRXkzVVE2V1JoenltdG1MdXBRTWh4enkKeThSMVFtNW1RUTI1T3lsNGl3TFZQdVE3Ty94dzh6UUdNR1pBY08zQ3NuYUM5Y3YxZFpnZzcvQ1BUTlpsY2RxZAp2OXFqNnpwVHFrT21OVnlFUGxPeFI3aHJ5UUtCZ1FEUm80WVNwcDRONHRlSWhIaG1uZGpYaVZkSDYyM3oxQldICnV1U3M0L0M2RDVjaUhWRTJYZnZ2cnQ4eEhBbEVycGtoaVFUZ1JzZHVwblVhVVFodUtaT1RNTXVGcDAzblA1cmMKUjlxY1MxQUVQUXZMVFNZN0dqaEptaExEVUM0WktGYmo4bXhkSWVBK09YL21mank4SFJTMWYxYncwR3owZCtvNQozVnlyOUU0ZllRS0JnUUNuY1QwckwxTGJCdDNTZFpMcmFDMC9uR2dXMkg1VWFia0JHZ09tN2hZSHFLa0VLZ0VoCnV1MGhjVGsyUml6K1NSQWdUanVtVXhqSHdYTWlSM3pJTlpMMmRQeGVqVDZMTjBqeUNlZHZDaEFrR24raVRUZnkKeFdxNXdEc2p2cnZNaTFjRWdLelVWVFc5YXdhcTVCMXJOZ3pYeEZVNk1EaDhsbDJabXJGYjNNU2dHUUtCZ0F1cQpmT2lHeXg3Y3M3L09GMkVtZ1kyay8rMXBwWW0vRUorbi85ZTdLNGMvSE5yeUpMWFF6eGRNZFBFbnJVQmNNdnRSCnc2cXpaWis3dGFLTVJkclRoM25XYWt6NnZYUVQ3d3M1R0dwQUtxakJ1T2xNVnNkTk16cXRUMFA5TDBPSklpUzMKTmQ2TTV3eXZhSFdzS3JjUkt6amFhRDBvYkJmQ29JOHR5VjFzVC9paEFvR0FWNHJzMU14d2VkRVVPTFo4Y0ZBQgpvYTE1MG41dVgwUGQ1Nm92enRXNHZoR2JyOVpjQ294dnR0a0VqQUNvTzdyanhBb1JyYVk1U1A0WTdHaHJxb043CkZYeFYrVVh0aGxyU21SQWUzS25ndzA4bkRpT0Fia0NZdFlGVGs3d3d3V0xYU2VYUWRCdUx1aWRqZ05PL2RhbkkKczUzVXU3OGlnRVdPa21YWThGYm1OVEU9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-ca-secret + namespace: nlk +type: kubernetes.io/tls +EOF +``` + +Delete: + +```bash +kubectl delete secret nlk-tls-ca-secret -n nlk +``` + +## Generation of TLS Configuration based on ConfigMap and Certificate Secrets + +Run the TLS configuration test harness: + +```bash +go run cmd/tls-configuration-test-harness/main.go +``` + +The test harness will iterate through the tls modes and show basic information about the generated TLS configuration. diff --git a/docs/DEMO/SLIDE-1.md b/docs/DEMO/SLIDE-1.md new file mode 100644 index 0000000..50766a2 --- /dev/null +++ b/docs/DEMO/SLIDE-1.md @@ -0,0 +1,5 @@ +# TLS in NLK + +NGINX Loadbalancer for Kubernetes, or NLK, is a Kubernetes Controller that uses NGINX Plus to load balance traffic to Kubernetes Services. + +[Next](SLIDE-2.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-2.md b/docs/DEMO/SLIDE-2.md new file mode 100644 index 0000000..74f6d97 --- /dev/null +++ b/docs/DEMO/SLIDE-2.md @@ -0,0 +1,9 @@ +## What is TLS? + +Transport Layer Security, or TLS, is a cryptographic protocol that provides end-to-end security of data sent between applications over the Internet; TLS is the successor to Secure Sockets Layer, or SSL. + +TLS serves two primary purposes: +* Authentications of actors in a communication channel; +* Encryption of data sent between actors in a communication channel; + +[Next](SLIDE-3.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-3.md b/docs/DEMO/SLIDE-3.md new file mode 100644 index 0000000..c9dcdf2 --- /dev/null +++ b/docs/DEMO/SLIDE-3.md @@ -0,0 +1,11 @@ +## TLS uses Certificates + +A Transport Layer Security (TLS) certificate, also known as an SSL certificate (Secure Sockets Layer), is a digital document that plays a crucial role in securing internet communications. Imagine it as a special, electronic passport for websites. + +A TLS certificate includes a "Chain of Trust" where there are multiple certificates that are used to verify the authenticity of the certificate. The certificate at the top of the chain is called the Root Certificate Authority (CA) Certificate. The Root CA Certificate is used to sign the certificates below it in the chain. The Root CA Certificate is not signed by any other certificate in the chain. + +The Root CA Certificate is used to sign the Intermediate CA Certificate. The Intermediate CA Certificate is used to sign the TLS Certificate. The TLS Certificate is used to sign the TLS Certificate Signing Request (CSR). The TLS Certificate Signing Request is used to sign the TLS Certificate. + +Root CA certificates can be expensive and are not required for most use cases. An alternative to purchasing an Intermediate CA certificate is to use a self-signed certificate. Self-signed certificates are free and can be used to sign TLS certificates. + +[Next](SLIDE-4.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-4.md b/docs/DEMO/SLIDE-4.md new file mode 100644 index 0000000..c5bc336 --- /dev/null +++ b/docs/DEMO/SLIDE-4.md @@ -0,0 +1,43 @@ +## One-way TLS and Mutual TLS + +There are two types of TLS: one-way TLS and mutual TLS. + +### One-way TLS + +One-way TLS is the most common type of TLS. In one-way TLS, the client verifies the server's identity, but the server does not verify the client's identity. One-way TLS is used to secure the connection between the client and the server. + +### Mutual TLS + +Mutual TLS is less common than one-way TLS. In mutual TLS, the client verifies the server's identity, and the server verifies the client's identity. Mutual TLS is used to secure the connection between the client and the server. + +The following diagram shows the difference between one-way TLS and mutual TLS. + +```mermaid +graph LR +CACertificate[CA Certificate] + + subgraph "One-way TLS" + NGINXPlusCert[NGINX Plus Certificate] + NLK[nginx-loadbalancer-kubernetes] + NGINXPlus[NGINX Plus] + NGINXPlusCert -->|Used by| NLK + NLK -->|Verifies| NGINXPlus + end + + subgraph "Mutual TLS" + NLKCert[NLK Certificate] + MNGINXPlusCert[NGINX Plus Certificate] + MNLK[nginx-loadbalancer-kubernetes] + MNGINXPlus[NGINX Plus] + NLKCert -->|Used by| MNGINXPlus + MNGINXPlus -->|Verifies| MNLK + MNGINXPlusCert -->|Used by| MNLK + MNLK -->|Verifies| MNGINXPlus + end + +CACertificate -->|Used for Signing| NLKCert +CACertificate -->|Used for Signing| NGINXPlusCert +CACertificate -->|Used for Signing| MNGINXPlusCert +``` + +[Next](SLIDE-5.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-5.md b/docs/DEMO/SLIDE-5.md new file mode 100644 index 0000000..fb0526a --- /dev/null +++ b/docs/DEMO/SLIDE-5.md @@ -0,0 +1,22 @@ +## TLS in NLK + +NLK supports three options for securing communications between the NLK and NGINX Plus: + +1. No TLS +2. TLS with self-signed certificates +3. TLS with certificates signed by a Certificate Authority (CA) + +Within the TLS options there are two sub-options: + +1. One-way TLS +2. Mutual TLS + +This gives five options for securing communications between the NLK and NGINX Plus. + +* No TLS: No authentication nor encryption is used. +* One-way TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* One-way TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* Mutual TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. +* Mutual TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. + +[Next](SLIDE-6.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-6.md b/docs/DEMO/SLIDE-6.md new file mode 100644 index 0000000..542f226 --- /dev/null +++ b/docs/DEMO/SLIDE-6.md @@ -0,0 +1,12 @@ +### Configuring TLS in NLK + +NLK uses a Kubernetes ConfigMap to configure TLS. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +There are three fields in the ConfigMap that are used to configure TLS: +* tls-mode: The TLS mode to use. Valid values are `none`, `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. +* ca-certificate: The CA certificate to use. This field is only used when `tls-mode` is set to `ss-tls` or `ss-mtls`. This certificate contains the "Chain of Trust" that is used to verify the authenticity of the TLS certificate. +* client-certificate: The client certificate to use. This field is only used when `tls-mode` is set to `ss-mtls` or `ca-mtls`. This certificate is provided to the NGINX Plus hosts for client authentication. + +The fields required depend on the `tls-mode` value. + +[Next](SLIDE-7.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-7.md b/docs/DEMO/SLIDE-7.md new file mode 100644 index 0000000..498494f --- /dev/null +++ b/docs/DEMO/SLIDE-7.md @@ -0,0 +1,5 @@ +### How NLK uses TLS + +NLK uses the `github.com/nginxinc/nginx-plus-go-client` to communicate with the NGINX Plus API. This library accepts a low-level HTTP client that is used for the actual communication with the NGINX Plus hosts. NLK generates a TLS Configuration based on the configured `tls-mode` and provides it to the low-level HTTP client. + +[Next](SLIDE-8.md) \ No newline at end of file diff --git a/docs/DEMO/SLIDE-8.md b/docs/DEMO/SLIDE-8.md new file mode 100644 index 0000000..796e048 --- /dev/null +++ b/docs/DEMO/SLIDE-8.md @@ -0,0 +1,2 @@ +# Demo + diff --git a/docs/tls/CA-MTLS.md b/docs/tls/CA-MTLS.md index 6692739..5f3be73 100644 --- a/docs/tls/CA-MTLS.md +++ b/docs/tls/CA-MTLS.md @@ -42,7 +42,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `tlsMode` and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ca-mtls` +For this mode, the `tls-mode` and `clientCertificate` fields need to be included. The `tls-mode` field should be set to `ca-mtls` and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -55,7 +55,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ca-mtls" + tls-mode: "ca-mtls" clientCertificate: "nlk-tls-client-secret" ``` diff --git a/docs/tls/CA-TLS.md b/docs/tls/CA-TLS.md index ed828cc..379b532 100644 --- a/docs/tls/CA-TLS.md +++ b/docs/tls/CA-TLS.md @@ -33,7 +33,7 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `tlsMode` fields needs to be included. The `tlsMode` field should be set to `ca-tls`. +For this mode, only the `tls-mode` fields needs to be included. The `tls-mode` field should be set to `ca-tls`. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -46,7 +46,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ca-tls" + tls-mode: "ca-tls" ``` ## Deployment diff --git a/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md b/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md new file mode 100644 index 0000000..729ff20 --- /dev/null +++ b/docs/tls/FIFTY-THOUSAND-FOOT-TLDR.md @@ -0,0 +1,102 @@ +# TLS in NLK + +NGINX Loadbalancer for Kubernetes, or NLK, is a Kubernetes Controller that uses NGINX Plus to load balance traffic to Kubernetes Services. + +## What is TLS? + +Transport Layer Security, or TLS, is a cryptographic protocol that provides end-to-end security of data sent between applications over the Internet; TLS is the successor to Secure Sockets Layer, or SSL[^1]. + +TLS serves two primary purposes: +* Authentications of actors in a communication channel; +* Encryption of data sent between actors in a communication channel; + +## TLS uses Certificates + +A Transport Layer Security (TLS) certificate, also known as an SSL certificate (Secure Sockets Layer), is a digital document that plays a crucial role in securing internet communications. Imagine it as a special, electronic passport for websites. + +A TLS certificate includes a "Chain of Trust" where there are multiple certificates that are used to verify the authenticity of the certificate. The certificate at the top of the chain is called the Root Certificate Authority (CA) Certificate. The Root CA Certificate is used to sign the certificates below it in the chain. The Root CA Certificate is not signed by any other certificate in the chain. + +The Root CA Certificate is used to sign the Intermediate CA Certificate. The Intermediate CA Certificate is used to sign the TLS Certificate. The TLS Certificate is used to sign the TLS Certificate Signing Request (CSR). The TLS Certificate Signing Request is used to sign the TLS Certificate. + +Root CA certificates can be expensive and are not required for most use cases. An alternative to purchasing an Intermediate CA certificate is to use a self-signed certificate. Self-signed certificates are free and can be used to sign TLS certificates. + +## One-way TLS and Mutual TLS + +There are two types of TLS: one-way TLS and mutual TLS. + +### One-way TLS + +One-way TLS is the most common type of TLS. In one-way TLS, the client verifies the server's identity, but the server does not verify the client's identity. One-way TLS is used to secure the connection between the client and the server. + +### Mutual TLS + +Mutual TLS is less common than one-way TLS. In mutual TLS, the client verifies the server's identity, and the server verifies the client's identity. Mutual TLS is used to secure the connection between the client and the server. + +The following diagram shows the difference between one-way TLS and mutual TLS. + +```mermaid +graph LR +CACertificate[CA Certificate] + + subgraph "One-way TLS" + NGINXPlusCert[NGINX Plus Certificate] + NLK[nginx-loadbalancer-kubernetes] + NGINXPlus[NGINX Plus] + NGINXPlusCert -->|Used by| NLK + NLK -->|Verifies| NGINXPlus + end + + subgraph "Mutual TLS" + NLKCert[NLK Certificate] + MNGINXPlusCert[NGINX Plus Certificate] + MNLK[nginx-loadbalancer-kubernetes] + MNGINXPlus[NGINX Plus] + NLKCert -->|Used by| MNGINXPlus + MNGINXPlus -->|Verifies| MNLK + MNGINXPlusCert -->|Used by| MNLK + MNLK -->|Verifies| MNGINXPlus + end + +CACertificate -->|Used for Signing| NLKCert +CACertificate -->|Used for Signing| NGINXPlusCert +CACertificate -->|Used for Signing| MNGINXPlusCert +``` + +## TLS in NLK + +NLK supports three options for securing communications between the NLK and NGINX Plus: + +1. No TLS +2. TLS with self-signed certificates +3. TLS with certificates signed by a Certificate Authority (CA) + +Within the TLS options there are two sub-options: + +1. One-way TLS +2. Mutual TLS + +This gives five options for securing communications between the NLK and NGINX Plus. + +* No TLS: No authentication nor encryption is used. +* One-way TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* One-way TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, but the NGINX Plus does not verify the NLK's identity. +* Mutual TLS with self-signed certificates: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. +* Mutual TLS with certificates signed by a CA: The NLK verifies the NGINX Plus's identity, and the NGINX Plus verifies the NLK's identity. + +### Configuring TLS in NLK + +NLK uses a Kubernetes ConfigMap to configure TLS. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. + +There are three fields in the ConfigMap that are used to configure TLS: +* tls-mode: The TLS mode to use. Valid values are `none`, `ss-tls`, `ss-mtls`, `ca-tls`, and `ca-mtls`. +* ca-certificate: The CA certificate to use. This field is only used when `tls-mode` is set to `ss-tls` or `ss-mtls`. This certificate contains the "Chain of Trust" that is used to verify the authenticity of the TLS certificate. +* client-certificate: The client certificate to use. This field is only used when `tls-mode` is set to `ss-mtls` or `ca-mtls`. This certificate is provided to the NGINX Plus hosts for client authentication. + +The fields required depend on the `tls-mode` value. + +### How NLK uses TLS + +NLK uses the `github.com/nginxinc/nginx-plus-go-client` to communicate with the NGINX Plus API. This library accepts a low-level HTTP client that is used for the actual communication with the NGINX Plus hosts. NLK generates a TLS Configuration based on the configured `tls-mode` and provides it to the low-level HTTP client. + + +[^1]: Source: [TLS Basics](https://www.internetsociety.org/deploy360/tls/basics/) \ No newline at end of file diff --git a/docs/tls/NO-TLS.md b/docs/tls/NO-TLS.md index 8831c84..1a4b7c8 100644 --- a/docs/tls/NO-TLS.md +++ b/docs/tls/NO-TLS.md @@ -21,7 +21,7 @@ No Kubernetes Secrets are required for this mode. NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is located in the `nlk` namespace. Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, only the `tlsMode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). +For this mode, only the `tls-mode` field needs to be included, and should be set to `no-tls` (or omitted altogether as this is the default mode). The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints) @@ -33,7 +33,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "no-tls" + tls-mode: "no-tls" ``` ## Deployment diff --git a/docs/tls/SS-MTLS.md b/docs/tls/SS-MTLS.md index c716889..bab97b0 100644 --- a/docs/tls/SS-MTLS.md +++ b/docs/tls/SS-MTLS.md @@ -45,9 +45,9 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, the `tlsMode`, `caCertificates`, and `clientCertificate` fields need to be included. The `tlsMode` field should be set to `ss-mtls`, -the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, -and the `clientCertificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. +For this mode, the `tls-mode`, `ca-certificates`, and `client-certificate` fields need to be included. The `tls-mode` field should be set to `ss-mtls`, +the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above, +and the `client-certificate` field should be set to the name of the Kubernetes Secret containing the Client certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -59,9 +59,9 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ss-mtls" - caCertificate: "nlk-tls-ca-secret" - clientCertificate: "nlk-tls-client-secret" + tls-mode: "ss-mtls" + ca-certificate: "nlk-tls-ca-secret" + client-certificate: "nlk-tls-client-secret" ``` ## Deployment diff --git a/docs/tls/SS-TLS.md b/docs/tls/SS-TLS.md index 7b92039..8f37d90 100644 --- a/docs/tls/SS-TLS.md +++ b/docs/tls/SS-TLS.md @@ -38,8 +38,8 @@ NLK is configured via a ConfigMap. The ConfigMap is named `nlk-config` and is lo Depending on which mode is chosen, certain fields will need to be updated in the NLK ConfigMap. -For this mode, both the `tlsMode` and `caCertificates` fields need to be included. The `tlsMode` field should be set to `ss-tls`, -and the `caCertificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. +For this mode, both the `tls-mode` and `ca-certificates` fields need to be included. The `tls-mode` field should be set to `ss-tls`, +and the `ca-certificates` field should be set to the name of the Kubernetes Secret containing the CA certificate created above. The following is an example of a ConfigMap for this mode (be sure to update the `nginx-hosts` field with the correct NGINX Plus API endpoints): @@ -52,7 +52,7 @@ metadata: namespace: nlk data: nginx-hosts: "http://10.1.1.4:9000/api,http://10.1.1.5:9000/api" - tlsMode: "ss-tls" + tls-mode: "ss-tls" caCertificate: "nlk-tls-ca-secret" ``` diff --git a/go.mod b/go.mod index 3075f62..c357b5a 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -26,12 +27,15 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect golang.org/x/sys v0.5.0 // indirect @@ -52,6 +56,7 @@ require ( ) replace ( + github.com/nginxinc/kubernetes-nginx-ingress/internal/certification => ./internal/certification github.com/nginxinc/kubernetes-nginx-ingress/internal/config => ./internal/config github.com/nginxinc/kubernetes-nginx-ingress/internal/translation => ./internal/translation github.com/nginxinc/kubernetes-nginx-ingress/internal/translation/nginxplus => ./internal/translation/nginxplus diff --git a/go.sum b/go.sum index a7efba6..8d71291 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -126,6 +128,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -157,6 +161,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -164,6 +170,7 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/internal/authentication/doc.go b/internal/authentication/doc.go new file mode 100644 index 0000000..109255e --- /dev/null +++ b/internal/authentication/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package authentication includes functionality to secure communications between NLK and NGINX Plus hosts. +*/ + +package authentication diff --git a/internal/authentication/factory.go b/internal/authentication/factory.go new file mode 100644 index 0000000..21b3458 --- /dev/null +++ b/internal/authentication/factory.go @@ -0,0 +1,115 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + * + * Factory for creating tls.Config objects based on the provided `tls-mode`. + */ + +package authentication + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" +) + +func NewTlsConfig(settings *configuration.Settings) (*tls.Config, error) { + logrus.Debugf("Creating TLS config for mode: '%s'", settings.TlsMode) + switch settings.TlsMode { + case "ss-tls": // needs ca cert + return buildSelfSignedTlsConfig(settings.Certificates) + + case "ss-mtls": // needs ca cert and client cert + return buildSelfSignedMtlsConfig(settings.Certificates) + + case "ca-tls": // needs nothing + return buildBasicTlsConfig(false), nil + + case "ca-mtls": // needs client cert + return buildCaTlsConfig(settings.Certificates) + + default: // no-tls, needs nothing + return buildBasicTlsConfig(true), nil + } +} + +func buildSelfSignedTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("Building self-signed TLS config") + certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) + if err != nil { + return nil, err + } + + return &tls.Config{ + InsecureSkipVerify: false, + RootCAs: certPool, + }, nil +} + +func buildSelfSignedMtlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("buildSelfSignedMtlsConfig Building self-signed mTLS config") + certPool, err := buildCaCertificatePool(certificates.GetCACertificate()) + if err != nil { + return nil, err + } + + certificate, err := buildCertificates(certificates.GetClientCertificate()) + if err != nil { + return nil, err + } + logrus.Debugf("buildSelfSignedMtlsConfig Certificate: %v", certificate) + + return &tls.Config{ + InsecureSkipVerify: false, + RootCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{certificate}, + }, nil +} + +func buildBasicTlsConfig(skipVerify bool) *tls.Config { + logrus.Debug("Building basic TLS config") + return &tls.Config{ + InsecureSkipVerify: skipVerify, + } +} + +func buildCaTlsConfig(certificates *certification.Certificates) (*tls.Config, error) { + logrus.Debug("Building CA TLS config") + certificate, err := buildCertificates(certificates.GetClientCertificate()) + if err != nil { + return nil, err + } + + return &tls.Config{ + InsecureSkipVerify: false, + Certificates: []tls.Certificate{certificate}, + }, nil +} + +func buildCertificates(privateKeyPEM []byte, certificatePEM []byte) (tls.Certificate, error) { + logrus.Debug("Building certificates") + return tls.X509KeyPair(certificatePEM, privateKeyPEM) +} + +func buildCaCertificatePool(caCert []byte) (*x509.CertPool, error) { + logrus.Debugf("Building CA certificate pool") + block, _ := pem.Decode(caCert) + if block == nil { + return nil, fmt.Errorf("failed to decode PEM block containing CA certificate") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, fmt.Errorf("error parsing certificate: %w", err) + } + + caCertPool := x509.NewCertPool() + caCertPool.AddCert(cert) + + return caCertPool, nil +} diff --git a/internal/authentication/factory_test.go b/internal/authentication/factory_test.go new file mode 100644 index 0000000..d106eb7 --- /dev/null +++ b/internal/authentication/factory_test.go @@ -0,0 +1,444 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +package authentication + +import ( + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "testing" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" + ClientCertificateSecretKey = "nlk-tls-client-secret" +) + +func TestTlsFactory_EmptyStringModeDefaultsToNoTls(t *testing.T) { + settings := configuration.Settings{ + TlsMode: "", + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != true { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be true`) + } +} + +func TestTlsFactory_UnspecifiedModeDefaultsToNoTls(t *testing.T) { + settings := configuration.Settings{} + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != true { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be true`) + } +} + +func TestTlsFactory_SelfSignedTlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) != 0 { + t.Fatalf(`tlsConfig.Certificates should be empty`) + } + + if tlsConfig.RootCAs == nil { + t.Fatalf(`tlsConfig.RootCAs should not be nil`) + } +} + +func TestTlsFactory_SelfSignedTlsModeCertPoolError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "failed to decode PEM block containing CA certificate" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedTlsModeCertPoolCertificateParseError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificateDataPEM()) + + settings := configuration.Settings{ + TlsMode: "ss-tls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "error parsing certificate: x509: inner and outer signature algorithm identifiers don't match" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedMtlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) == 0 { + t.Fatalf(`tlsConfig.Certificates should not be empty`) + } + + if tlsConfig.RootCAs == nil { + t.Fatalf(`tlsConfig.RootCAs should not be nil`) + } +} + +func TestTlsFactory_SelfSignedMtlsModeCertPoolError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(invalidCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "failed to decode PEM block containing CA certificate" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_SelfSignedMtlsModeClientCertificateError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ss-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "tls: failed to find any PEM data in certificate input" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +func TestTlsFactory_CaTlsMode(t *testing.T) { + settings := configuration.Settings{ + TlsMode: "ca-tls", + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) != 0 { + t.Fatalf(`tlsConfig.Certificates should be empty`) + } + + if tlsConfig.RootCAs != nil { + t.Fatalf(`tlsConfig.RootCAs should be nil`) + } +} + +func TestTlsFactory_CaMtlsMode(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), clientCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + CaCertificateSecretKey: CaCertificateSecretKey, + ClientCertificateSecretKey: ClientCertificateSecretKey, + }, + } + + tlsConfig, err := NewTlsConfig(&settings) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if tlsConfig == nil { + t.Fatalf(`tlsConfig should not be nil`) + } + + if tlsConfig.InsecureSkipVerify != false { + t.Fatalf(`tlsConfig.InsecureSkipVerify should be false`) + } + + if len(tlsConfig.Certificates) == 0 { + t.Fatalf(`tlsConfig.Certificates should not be empty`) + } + + if tlsConfig.RootCAs != nil { + t.Fatalf(`tlsConfig.RootCAs should be nil`) + } +} + +func TestTlsFactory_CaMtlsModeClientCertificateError(t *testing.T) { + certificates := make(map[string]map[string][]byte) + certificates[CaCertificateSecretKey] = buildCaCertificateEntry(caCertificatePEM()) + certificates[ClientCertificateSecretKey] = buildClientCertificateEntry(clientKeyPEM(), invalidCertificatePEM()) + + settings := configuration.Settings{ + TlsMode: "ca-mtls", + Certificates: &certification.Certificates{ + Certificates: certificates, + }, + } + + _, err := NewTlsConfig(&settings) + if err == nil { + t.Fatalf(`Expected an error`) + } + + if err.Error() != "tls: failed to find any PEM data in certificate input" { + t.Fatalf(`Unexpected error message: %v`, err) + } +} + +// caCertificatePEM returns a PEM-encoded CA certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func caCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIDTzCCAjcCFA4Zdj3E9TdjOP48eBRDGRLfkj7CMA0GCSqGSIb3DQEBCwUAMGQx +CzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0 +dGxlMQ4wDAYDVQQKDAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFu +Y2VzMB4XDTIzMDkyOTE3MTY1MVoXDTIzMTAyOTE3MTY1MVowZDELMAkGA1UEBhMC +VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxDjAMBgNV +BAoMBU5HSU5YMR4wHAYDVQQLDBVDb21tdW5pdHkgJiBBbGxpYW5jZXMwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwlI4ZvJ/6hvqULFVL+1ZSRDTPQ48P +umehJhPz6xPhC9UkeTe2FZxm2Rsi1I5QXm/bTG2OcX775jgXzae9NQjctxwrz4Ks +LOWUvRkkfhQR67xk0Noux76/9GWGnB+Fapn54tlWql6uHQfOu1y7MCRkZ27zHbkk +lq4Oa2RmX8rIyECWgbTyL0kETBVJU8bYORQ5JjhRlz08inq3PggY8blrehIetrWN +dw+gzcqdvAI2uSCodHTHM/77KipnYmPiSiDjSDRlXdxTG8JnyIB78IoH/sw6RyBm +CvVa3ytvKziXAvbBoXq5On5WmMRF97p/MmBc53ExMuDZjA4fisnViS0PAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAJeoa2P59zopLjBInx/DnWn1N1CmFLb0ejKxG2jh +cOw15Sx40O0XrtrAto38iu4R/bkBeNCSUILlT+A3uYDila92Dayvls58WyIT3meD +G6+Sx/QDF69+4AXpVy9mQ+hxcofpFA32+GOMXwmk2OrAcdSkkGSBhZXgvTpQ64dl +xSiQ5EQW/K8LoBoEOXfjIZJNPORgKn5MI09AY7/47ycKDKTUU2yO8AtIHYKttw0x +kfIg7QOdo1F9IXVpGjJI7ynyrgsCEYxMoDyH42Dq84eKgrUFLEXemEz8hgdFgK41 +0eUYhAtzWHbRPBp+U/34CQoZ5ChNFp2YipvtXrzKE8KLkuM= +-----END CERTIFICATE----- +` +} + +func invalidCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIClzCCAX+gAwIBAgIJAIfPhC0RG6CwMA0GCSqGSIb3DQEBCwUAMBkxFzAVBgNV +BAMMDm9pbCBhdXRob3JpdHkwHhcNMjAwNDA3MTUwOTU1WhcNMjEwNDA2MTUwOTU1 +WjBMMSAwHgYDVQQLDBd5b3VuZy1jaGFsbGVuZ2UgdGVzdCBjb25zdW1lczEfMB0G +A1UECwwWc28wMS5jb3Jwb3JhdGlvbnNvY2lhbDEhMB8GA1UEAwwYc29tMS5jb3Jw +b3JhdGlvbnNvY2lhbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDGRX31uzy+yLUOz7wOJHHm2dzrDgUbC6RZDjURvZxyt2Zi5wYWsEB5r5YhN7L0 +y1R9f+MGwNITIz9nYZuU/PLFOvzF5qX7A8TbdgjZEqvXe2NZ9J2z3iWvYQLN8Py3 +nv/Y6wadgXEBRCNNuIg/bQ9XuOr9tfB6j4Ut1GLU0eIlV/L3Rf9Y6SgrAl+58ITj +Wrg3Js/Wz3J2JU4qBD8U4I3XvUyfnX2SAG8Llm4KBuYz7g63Iu05s6RnmG+Xhu2T +5f2DWZUeATWbAlUW/M4NLO1+5H0gOr0TGulETQ6uElMchT7s/H6Rv1CV+CNCCgEI +adRjWJq9yQ+KrE+urSMCXu8XAgMBAAGjUzBRMB0GA1UdDgQWBBRb40pKGU4lNvqB +1f5Mz3t0N/K3hzAfBgNVHSMEGDAWgBRb40pKGU4lNvqB1f5Mz3t0N/K3hzAPBgNV +HREECDAGhwQAAAAAAAAwCgYIKoZIzj0EAwIDSAAwRQIhAP3ST/mXyRXsU2ciRoE +gE6trllODFY+9FgT6UbF2TwzAiAAuaUxtbk6uXLqtD5NtXqOQf0Ckg8GQxc5V1G2 +9PqTXQ== +-----END CERTIFICATE----- +` +} + +// Yoinked from https://cs.opensource.google/go/go/+/refs/tags/go1.21.1:src/crypto/x509/x509_test.go, line 3385 +// This allows the `buildCaCertificatePool(...)` --> `x509.ParseCertificate(...)` call error branch to be covered. +func invalidCertificateDataPEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIBBzCBrqADAgECAgEAMAoGCCqGSM49BAMCMAAwIhgPMDAwMTAxMDEwMDAwMDBa +GA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOqV +EDuVXxwZgIU3+dOwv1SsMu0xuV48hf7xmK8n7sAMYgllB+96DnPqBeboJj4snYnx +0AcE0PDVQ1l4Z3YXsQWjFTATMBEGA1UdEQEB/wQHMAWCA2FzZDAKBggqhkjOPQQD +AwNIADBFAiBi1jz/T2HT5nAfrD7zsgR+68qh7Erc6Q4qlxYBOgKG4QIhAOtjIn+Q +tA+bq+55P3ntxTOVRq0nv1mwnkjwt9cQR9Fn +-----END CERTIFICATE----- +` +} + +// clientCertificatePEM returns a PEM-encoded client certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientCertificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// clientKeyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func clientKeyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} + +func buildClientCertificateEntry(keyPEM, certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + certification.CertificateKeyKey: []byte(keyPEM), + } +} + +func buildCaCertificateEntry(certificatePEM string) map[string][]byte { + return map[string][]byte{ + certification.CertificateKey: []byte(certificatePEM), + } +} diff --git a/internal/certification/certificates.go b/internal/certification/certificates.go new file mode 100644 index 0000000..93321a1 --- /dev/null +++ b/internal/certification/certificates.go @@ -0,0 +1,195 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + * + * Establishes a Watcher for the Kubernetes Secrets that contain the various certificates and keys used to generate a tls.Config object; + * exposes the certificates and keys. + */ + +package certification + +import ( + "context" + "fmt" + "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +// TODO: This needs to use the settings for the secret names... + +const ( + // SecretsNamespace is the value used to filter the Secrets Resource in the Informer. + SecretsNamespace = "nlk" + + // CertificateKey is the key for the certificate in the Secret. + CertificateKey = "tls.crt" + + // CertificateKeyKey is the key for the certificate key in the Secret. + CertificateKeyKey = "tls.key" +) + +type Certificates struct { + Certificates map[string]map[string][]byte + + // Context is the context used to control the application. + Context context.Context + + // CaCertificateSecretKey is the name of the Secret that contains the Certificate Authority certificate. + CaCertificateSecretKey string + + // ClientCertificateSecretKey is the name of the Secret that contains the Client certificate. + ClientCertificateSecretKey string + + // informer is the SharedInformer used to watch for changes to the Secrets . + informer cache.SharedInformer + + // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. + k8sClient kubernetes.Interface + + // eventHandlerRegistration is the object used to track the event handlers with the SharedInformer. + eventHandlerRegistration cache.ResourceEventHandlerRegistration +} + +// NewCertificates factory method that returns a new Certificates object. +func NewCertificates(ctx context.Context, k8sClient kubernetes.Interface) (*Certificates, error) { + return &Certificates{ + k8sClient: k8sClient, + Context: ctx, + Certificates: nil, + }, nil +} + +// GetCACertificate returns the Certificate Authority certificate. +func (c *Certificates) GetCACertificate() []byte { + bytes := c.Certificates[c.CaCertificateSecretKey][CertificateKey] + + return bytes +} + +// GetClientCertificate returns the Client certificate and key. +func (c *Certificates) GetClientCertificate() ([]byte, []byte) { + keyBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKeyKey] + certificateBytes := c.Certificates[c.ClientCertificateSecretKey][CertificateKey] + + return keyBytes, certificateBytes +} + +// Initialize initializes the Certificates object. Sets up a SharedInformer for the Secrets Resource. +func (c *Certificates) Initialize() error { + logrus.Info("Certificates::Initialize") + + var err error + + c.Certificates = make(map[string]map[string][]byte) + + informer, err := c.buildInformer() + if err != nil { + return fmt.Errorf(`error occurred building an informer: %w`, err) + } + + c.informer = informer + + err = c.initializeEventHandlers() + if err != nil { + return fmt.Errorf(`error occurred initializing event handlers: %w`, err) + } + + return nil +} + +// Run starts the SharedInformer. +func (c *Certificates) Run() error { + logrus.Info("Certificates::Run") + + if c.informer == nil { + return fmt.Errorf(`initialize must be called before Run`) + } + + c.informer.Run(c.Context.Done()) + + <-c.Context.Done() + + return nil +} + +func (c *Certificates) buildInformer() (cache.SharedInformer, error) { + logrus.Debug("Certificates::buildInformer") + + options := informers.WithNamespace(SecretsNamespace) + factory := informers.NewSharedInformerFactoryWithOptions(c.k8sClient, 0, options) + informer := factory.Core().V1().Secrets().Informer() + + return informer, nil +} + +func (c *Certificates) initializeEventHandlers() error { + logrus.Debug("Certificates::initializeEventHandlers") + + var err error + + handlers := cache.ResourceEventHandlerFuncs{ + AddFunc: c.handleAddEvent, + DeleteFunc: c.handleDeleteEvent, + UpdateFunc: c.handleUpdateEvent, + } + + c.eventHandlerRegistration, err = c.informer.AddEventHandler(handlers) + if err != nil { + return fmt.Errorf(`error occurred registering event handlers: %w`, err) + } + + return nil +} + +func (c *Certificates) handleAddEvent(obj interface{}) { + logrus.Debug("Certificates::handleAddEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleAddEvent: unable to cast object to Secret") + return + } + + c.Certificates[secret.Name] = map[string][]byte{} + + for k, v := range secret.Data { + c.Certificates[secret.Name][k] = v + } + + logrus.Debugf("Certificates::handleAddEvent: certificates (%d)", len(c.Certificates)) +} + +func (c *Certificates) handleDeleteEvent(obj interface{}) { + logrus.Debug("Certificates::handleDeleteEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleDeleteEvent: unable to cast object to Secret") + return + } + + if c.Certificates[secret.Name] != nil { + delete(c.Certificates, secret.Name) + } + + logrus.Debugf("Certificates::handleDeleteEvent: certificates (%d)", len(c.Certificates)) +} + +func (c *Certificates) handleUpdateEvent(obj interface{}, obj2 interface{}) { + logrus.Debug("Certificates::handleUpdateEvent") + + secret, ok := obj.(*corev1.Secret) + if !ok { + logrus.Errorf("Certificates::handleUpdateEvent: unable to cast object to Secret") + return + } + + for k, v := range secret.Data { + c.Certificates[secret.Name][k] = v + } + + logrus.Debugf("Certificates::handleUpdateEvent: certificates (%d)", len(c.Certificates)) +} diff --git a/internal/certification/certificates_test.go b/internal/certification/certificates_test.go new file mode 100644 index 0000000..b741c52 --- /dev/null +++ b/internal/certification/certificates_test.go @@ -0,0 +1,244 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +package certification + +import ( + "context" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/tools/cache" + "testing" + "time" +) + +const ( + CaCertificateSecretKey = "nlk-tls-ca-secret" +) + +func TestNewCertificate(t *testing.T) { + ctx := context.Background() + + certificates, err := NewCertificates(ctx, nil) + + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + if certificates == nil { + t.Fatalf(`certificates should not be nil`) + } +} + +func TestCertificates_Initialize(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + err = certificates.Initialize() + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } +} + +func TestCertificates_RunWithoutInitialize(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`Unexpected error: %v`, err) + } + + err = certificates.Run() + if err == nil { + t.Fatalf(`Expected error`) + } + + if err.Error() != `initialize must be called before Run` { + t.Fatalf(`Unexpected error: %v`, err) + } +} + +func TestCertificates_EmptyCertificates(t *testing.T) { + certificates, err := NewCertificates(context.Background(), nil) + if err != nil { + t.Fatalf(`error building Certificates: %v`, err) + } + + err = certificates.Initialize() + if err != nil { + t.Fatalf(`error Initializing Certificates: %v`, err) + } + + caBytes := certificates.GetCACertificate() + if caBytes != nil { + t.Fatalf(`Expected nil CA certificate`) + } + + clientKey, clientCert := certificates.GetClientCertificate() + if clientKey != nil { + t.Fatalf(`Expected nil client key`) + } + if clientCert != nil { + t.Fatalf(`Expected nil client certificate`) + } +} + +func TestCertificates_ExerciseHandlers(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + k8sClient := fake.NewSimpleClientset() + + certificates, err := NewCertificates(ctx, k8sClient) + if err != nil { + t.Fatalf(`error building Certificates: %v`, err) + } + + _ = certificates.Initialize() + + certificates.CaCertificateSecretKey = CaCertificateSecretKey + //certificates.ClientCertificateSecretKey = "nlk-tls-client-secret" + + go func() { + err := certificates.Run() + if err != nil { + t.Fatalf("error running Certificates: %v", err) + } + }() + + cache.WaitForCacheSync(ctx.Done(), certificates.informer.HasSynced) + + secret := buildSecret() + + /* -- Test Create -- */ + + created, err := k8sClient.CoreV1().Secrets(SecretsNamespace).Create(ctx, secret, metav1.CreateOptions{}) + if err != nil { + t.Fatalf(`error creating the Secret: %v`, err) + } + + if created.Name != secret.Name { + t.Fatalf(`Expected name %v, got %v`, secret.Name, created.Name) + } + + time.Sleep(2 * time.Second) + + caBytes := certificates.GetCACertificate() + if caBytes == nil { + t.Fatalf(`Expected non-nil CA certificate`) + } + + /* -- Test Update -- */ + + secret.Labels = map[string]string{"updated": "true"} + _, err = k8sClient.CoreV1().Secrets(SecretsNamespace).Update(ctx, secret, metav1.UpdateOptions{}) + if err != nil { + t.Fatalf(`error updating the Secret: %v`, err) + } + + time.Sleep(2 * time.Second) + + caBytes = certificates.GetCACertificate() + if caBytes == nil { + t.Fatalf(`Expected non-nil CA certificate`) + } + + /* -- Test Delete -- */ + + err = k8sClient.CoreV1().Secrets(SecretsNamespace).Delete(ctx, secret.Name, metav1.DeleteOptions{}) + if err != nil { + t.Fatalf(`error deleting the Secret: %v`, err) + } + + time.Sleep(2 * time.Second) + + caBytes = certificates.GetCACertificate() + if caBytes != nil { + t.Fatalf(`Expected nil CA certificate, got: %v`, caBytes) + } +} + +func buildSecret() *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: CaCertificateSecretKey, + Namespace: SecretsNamespace, + }, + Data: map[string][]byte{ + CertificateKey: []byte(certificatePEM()), + CertificateKeyKey: []byte(keyPEM()), + }, + Type: corev1.SecretTypeTLS, + } +} + +// certificatePEM returns a PEM-encoded client certificate. +// Note: The certificate is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func certificatePEM() string { + return ` +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgIULDFXwGrTohN/PRao2rSLk9VxFdgwDQYJKoZIhvcNAQEL +BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcM +CUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVyMRQwEgYDVQQLDAtEZXZlbG9wbWVu +dDAeFw0yMzA5MjkxNzA3NTRaFw0yNDA5MjgxNzA3NTRaMGQxCzAJBgNVBAYTAlVT +MRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMQ4wDAYDVQQK +DAVOR0lOWDEeMBwGA1UECwwVQ29tbXVuaXR5ICYgQWxsaWFuY2VzMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoqNuEZ6+TcFrmzcwp8u8mzk0jPd47GKk +H9wwdkFCzGdd8KJkFQhzLyimZIWkRDYmhaxZd76jKGBpdfyivR4e4Mi5WYlpPGMI +ppM7/rMYP8yn04tkokAazbqjOTlF8NUKqGQwqAN4Z/PvoG2HyP9omGpuLWTbjKto +oGr5aPBIhzlICU3OjHn6eKaekJeAYBo3uQFYOxCjtE9hJLDOY4q7zomMJfYoeoA2 +Afwkx1Lmozp2j/esB52/HlCKVhAOzZsPzM+E9eb1Q722dUed4OuiVYSfrDzeImrA +TufzTBTMEpFHCtdBGocZ3LRd9qmcP36ZCMsJNbYnQZV3XsI4JhjjHwIDAQABo4G8 +MIG5MBMGA1UdJQQMMAoGCCsGAQUFBwMCMB0GA1UdDgQWBBRDl4jeiE1mJDPrYmQx +g2ndkWxpYjCBggYDVR0jBHsweaFhpF8wXTELMAkGA1UEBhMCVVMxEzARBgNVBAgM +Cldhc2hpbmd0b24xEjAQBgNVBAcMCUluZGlhbm9sYTEPMA0GA1UECgwGV2FnbmVy +MRQwEgYDVQQLDAtEZXZlbG9wbWVudIIUNxx2Mr+PKXiF3d2i51fb/rnWbBgwDQYJ +KoZIhvcNAQELBQADggEBAL0wS6LkFuqGDlhaTGnAXRwRDlC6uwrm8wNWppaw9Vqt +eaZGFzodcCFp9v8jjm1LsTv7gEUBnWtn27LGP4GJSpZjiq6ulJypBxo/G0OkMByK +ky4LeGY7/BQzjzHdfXEq4gwfC45ni4n54uS9uzW3x+AwLSkxPtBxSwxhtwBLo9aE +Ql4rHUoWc81mhGO5mMZBaorxZXps1f3skfP+wZX943FIMt5gz4hkxwFp3bI/FrqH +R8DLUlCzBA9+7WIFD1wi25TV+Oyq3AjT/KiVmR+umrukhnofCWe8JiVpb5iJcd2k +Rc7+bvyb5OCnJdEX08XGWmF2/OFKLrCzLH1tQxk7VNE= +-----END CERTIFICATE----- +` +} + +// keyPEM returns a PEM-encoded client key. +// Note: The key is self-signed and generated explicitly for tests, +// it is not used anywhere else. +func keyPEM() string { + return ` +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCio24Rnr5NwWub +NzCny7ybOTSM93jsYqQf3DB2QULMZ13womQVCHMvKKZkhaRENiaFrFl3vqMoYGl1 +/KK9Hh7gyLlZiWk8Ywimkzv+sxg/zKfTi2SiQBrNuqM5OUXw1QqoZDCoA3hn8++g +bYfI/2iYam4tZNuMq2igavlo8EiHOUgJTc6Mefp4pp6Ql4BgGje5AVg7EKO0T2Ek +sM5jirvOiYwl9ih6gDYB/CTHUuajOnaP96wHnb8eUIpWEA7Nmw/Mz4T15vVDvbZ1 +R53g66JVhJ+sPN4iasBO5/NMFMwSkUcK10EahxnctF32qZw/fpkIywk1tidBlXde +wjgmGOMfAgMBAAECggEAA+R2b2yFsHW3HhVhkDqDjpF9bPxFRB8OP4b1D/d64kp9 +CJPSYmB75T6LUO+T4WAMZvmbgI6q9/3quDyuJmmQop+bNAXiY2QZYmc2sd9Wbrx2 +rczxwSJYoeDcJDP3NQ7cPPB866B9ortHWmcUr15RgghWD7cQvBqkG+bDhlvt2HKg +NZmL6R0U1bVAlRMtFJiEdMHuGnPmoDU5IGc1fKjsgijLeMboUrEaXWINoEm8ii5e +/mnsfLCBmeJAsKuXxL8/1UmvWYE/ltDfYBVclKhcH2UWTZv7pdRtHnu49lkZivUB +ZvH2DHsSMjXj6+HHr6RcRGmnMDyfhJFPCjOdTjf4oQKBgQDeYLWZx22zGXgfb7md +MhdKed9GxMJHzs4jDouqrHy0w95vwMi7RXgeKpKXiCruqSEB/Trtq01f7ekh0mvJ +Ys0h4A5tkrT5BVVBs+65uF/kSF2z/CYGNRhAABO7UM+B1e3tlnjfjeb/M78IcFbT +FyBN90A/+a9JGZ4obt3ack3afwKBgQC7OncnXC9L5QCWForJWQCNO3q3OW1Gaoxe +OAnmnPSJ7NUd7xzDNE8pzBUWXysZCoRU3QNElcQfzHWtZx1iqJPk3ERK2awNsnV7 +X2Fu4vHzIr5ZqVnM8NG7+iWrxRLf+ctcEvPiqRYo+g+r5tTGJqWh2nh9W7iQwwwE +1ikoxFBnYQKBgCbDdOR5fwXZSrcwIorkUGsLE4Cii7s4sXYq8u2tY4+fFQcl89ex +JF8dzK/dbJ5tnPNb0Qnc8n/mWN0scN2J+3gMNnejOyitZU8urk5xdUW115+oNHig +iLmfSdE9JO7c+7yOnkNZ2QpjWsl9y6TAQ0FT+D8upv93F7q0mLebdTbBAoGBALmp +r5EThD9RlvQ+5F/oZ3imO/nH88n5TLr9/St4B7NibLAjdrVIgRwkqeCmfRl26WUy +SdRQY81YtnU/JM+59fbkSsCi/FAU4RV3ryoD2QRPNs249zkYshMjawncAuyiS/xB +OyJQpI3782B3JhZdKrDG8eb19p9vG9MMAILRsh3hAoGASCvmq10nHHGFYTerIllQ +sohNaw3KDlQTkpyOAztS4jOXwvppMXbYuCznuJbHz0NEM2ww+SiA1RTvD/gosYYC +mMgqRga/Qu3b149M3wigDjK+RAcyuNGZN98bqU/UjJLjqH6IMutt59+9XNspcD96 +z/3KkMx4uqJXZyvQrmkolSg= +-----END PRIVATE KEY----- +` +} diff --git a/internal/certification/doc.go b/internal/certification/doc.go new file mode 100644 index 0000000..3388ea0 --- /dev/null +++ b/internal/certification/doc.go @@ -0,0 +1,10 @@ +/* + * Copyright 2023 F5 Inc. All rights reserved. + * Use of this source code is governed by the Apache License that can be found in the LICENSE file. + */ + +/* +Package certification includes functionality to access the Secrets containing the TLS Certificates. +*/ + +package certification diff --git a/internal/communication/client.go b/internal/communication/factory.go similarity index 71% rename from internal/communication/client.go rename to internal/communication/factory.go index fb7d80d..9a3d411 100644 --- a/internal/communication/client.go +++ b/internal/communication/factory.go @@ -7,6 +7,9 @@ package communication import ( "crypto/tls" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/authentication" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "github.com/sirupsen/logrus" netHttp "net/http" "time" ) @@ -14,9 +17,9 @@ import ( // NewHttpClient is a factory method to create a new Http Client with a default configuration. // RoundTripper is a wrapper around the default net/communication Transport to add additional headers, in this case, // the Headers are configured for JSON. -func NewHttpClient() (*netHttp.Client, error) { +func NewHttpClient(settings *configuration.Settings) (*netHttp.Client, error) { headers := NewHeaders() - tlsConfig := NewTlsConfig() + tlsConfig := NewTlsConfig(settings) transport := NewTransport(tlsConfig) roundTripper := NewRoundTripper(headers, transport) @@ -38,8 +41,14 @@ func NewHeaders() []string { // NewTlsConfig is a factory method to create a new basic Tls Config. // More attention should be given to the use of `InsecureSkipVerify: true`, as it is not recommended for production use. -func NewTlsConfig() *tls.Config { - return &tls.Config{InsecureSkipVerify: true} +func NewTlsConfig(settings *configuration.Settings) *tls.Config { + tlsConfig, err := authentication.NewTlsConfig(settings) + if err != nil { + logrus.Warnf("Failed to create TLS config: %v", err) + return &tls.Config{InsecureSkipVerify: true} + } + + return tlsConfig } // NewTransport is a factory method to create a new basic Http Transport. diff --git a/internal/communication/client_test.go b/internal/communication/factory_test.go similarity index 75% rename from internal/communication/client_test.go rename to internal/communication/factory_test.go index 2f3a82f..f25abef 100644 --- a/internal/communication/client_test.go +++ b/internal/communication/factory_test.go @@ -6,11 +6,16 @@ package communication import ( + "context" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "k8s.io/client-go/kubernetes/fake" "testing" ) func TestNewHttpClient(t *testing.T) { - client, err := NewHttpClient() + k8sClient := fake.NewSimpleClientset() + settings, err := configuration.NewSettings(context.Background(), k8sClient) + client, err := NewHttpClient(settings) if err != nil { t.Fatalf(`Unexpected error: %v`, err) @@ -41,20 +46,10 @@ func TestNewHeaders(t *testing.T) { } } -func TestNewTlsConfig(t *testing.T) { - config := NewTlsConfig() - - if config == nil { - t.Fatalf(`config should not be nil`) - } - - if !config.InsecureSkipVerify { - t.Fatalf(`config.InsecureSkipVerify should be true`) - } -} - func TestNewTransport(t *testing.T) { - config := NewTlsConfig() + k8sClient := fake.NewSimpleClientset() + settings, _ := configuration.NewSettings(context.Background(), k8sClient) + config := NewTlsConfig(settings) transport := NewTransport(config) if transport == nil { diff --git a/internal/communication/roundtripper_test.go b/internal/communication/roundtripper_test.go index d28cf53..4185b6d 100644 --- a/internal/communication/roundtripper_test.go +++ b/internal/communication/roundtripper_test.go @@ -7,13 +7,18 @@ package communication import ( "bytes" + "context" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/configuration" + "k8s.io/client-go/kubernetes/fake" netHttp "net/http" "testing" ) func TestNewRoundTripper(t *testing.T) { + k8sClient := fake.NewSimpleClientset() + settings, _ := configuration.NewSettings(context.Background(), k8sClient) headers := NewHeaders() - transport := NewTransport(NewTlsConfig()) + transport := NewTransport(NewTlsConfig(settings)) roundTripper := NewRoundTripper(headers, transport) if roundTripper == nil { @@ -42,8 +47,10 @@ func TestNewRoundTripper(t *testing.T) { } func TestRoundTripperRoundTrip(t *testing.T) { + k8sClient := fake.NewSimpleClientset() + settings, err := configuration.NewSettings(context.Background(), k8sClient) headers := NewHeaders() - transport := NewTransport(NewTlsConfig()) + transport := NewTransport(NewTlsConfig(settings)) roundTripper := NewRoundTripper(headers, transport) request, err := NewRequest("GET", "http://example.com", nil) diff --git a/internal/configuration/settings.go b/internal/configuration/settings.go index 12c9823..8c8874a 100644 --- a/internal/configuration/settings.go +++ b/internal/configuration/settings.go @@ -8,8 +8,10 @@ package configuration import ( "context" "fmt" + "github.com/nginxinc/kubernetes-nginx-ingress/internal/certification" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -22,6 +24,9 @@ const ( // ConfigMapsNamespace is the value used to filter the ConfigMaps Resource in the Informer. ConfigMapsNamespace = "nlk" + // ConfigMapName is the name of the ConfigMap that contains the configuration for the application. + ConfigMapName = "nlk-config" + // ResyncPeriod is the value used to set the resync period for the Informer. ResyncPeriod = 0 @@ -104,8 +109,14 @@ type Settings struct { // NginxPlusHosts is a list of Nginx Plus hosts that will be used to update the Border Servers. NginxPlusHosts []string + // TlsMode is the value used to determine which of the five TLS modes will be used to communicate with the Border Servers (see: ../../docs/tls/README.md). + TlsMode string + + // Certificates is the object used to retrieve the certificates and keys used to communicate with the Border Servers. + Certificates *certification.Certificates + // K8sClient is the Kubernetes client used to communicate with the Kubernetes API. - K8sClient *kubernetes.Clientset + K8sClient kubernetes.Interface // informer is the SharedInformer used to watch for changes to the ConfigMap . informer cache.SharedInformer @@ -124,10 +135,12 @@ type Settings struct { } // NewSettings creates a new Settings object with default values. -func NewSettings(ctx context.Context, k8sClient *kubernetes.Clientset) (*Settings, error) { +func NewSettings(ctx context.Context, k8sClient kubernetes.Interface) (*Settings, error) { settings := &Settings{ - Context: ctx, - K8sClient: k8sClient, + Context: ctx, + K8sClient: k8sClient, + TlsMode: "", + Certificates: nil, Handler: HandlerSettings{ RetryCount: 5, Threads: 1, @@ -164,6 +177,29 @@ func (s *Settings) Initialize() error { var err error + certificates, err := certification.NewCertificates(s.Context, s.K8sClient) + if err != nil { + return fmt.Errorf(`error occurred creating certificates: %w`, err) + } + + err = certificates.Initialize() + if err != nil { + return fmt.Errorf(`error occurred initializing certificates: %w`, err) + } + + s.Certificates = certificates + + go certificates.Run() + + logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieving nlk-config ConfigMap") + configMap, err := s.K8sClient.CoreV1().ConfigMaps(ConfigMapsNamespace).Get(s.Context, "nlk-config", metav1.GetOptions{}) + if err != nil { + return err + } + + s.handleUpdateEvent(nil, configMap) + logrus.Debug(">>>>>>>>>> Settings::Initialize: retrieved nlk-config ConfigMap") + informer, err := s.buildInformer() if err != nil { return fmt.Errorf(`error occurred building ConfigMap informer: %w`, err) @@ -220,32 +256,63 @@ func (s *Settings) initializeEventListeners() error { func (s *Settings) handleAddEvent(obj interface{}) { logrus.Debug("Settings::handleAddEvent") - s.handleUpdateEvent(obj, nil) + if _, yes := isOurConfig(obj); yes { + s.handleUpdateEvent(nil, obj) + } } -func (s *Settings) handleDeleteEvent(_ interface{}) { +func (s *Settings) handleDeleteEvent(obj interface{}) { logrus.Debug("Settings::handleDeleteEvent") - s.updateHosts([]string{}) + if _, yes := isOurConfig(obj); yes { + s.updateHosts([]string{}) + } } -func (s *Settings) handleUpdateEvent(obj interface{}, _ interface{}) { +func (s *Settings) handleUpdateEvent(_ interface{}, obj interface{}) { logrus.Debug("Settings::handleUpdateEvent") - configMap, ok := obj.(*corev1.ConfigMap) - if !ok { - logrus.Errorf("Settings::handleUpdateEvent: could not convert obj to ConfigMap") + configMap, yes := isOurConfig(obj) + if !yes { return } hosts, found := configMap.Data["nginx-hosts"] - if !found { - logrus.Errorf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") - return + if found { + newHosts := s.parseHosts(hosts) + s.updateHosts(newHosts) + } else { + logrus.Warnf("Settings::handleUpdateEvent: nginx-hosts key not found in ConfigMap") + } + + tlsMode, found := configMap.Data["tls-mode"] + if found { + s.TlsMode = tlsMode + logrus.Debugf("Settings::handleUpdateEvent: tls-mode: %s", s.TlsMode) + } else { + s.TlsMode = "no-tls" + logrus.Warnf("Settings::handleUpdateEvent: tls-mode key not found in ConfigMap, defaulting to 'no-tls'") } - newHosts := s.parseHosts(hosts) - s.updateHosts(newHosts) + caCertificateSecretKey, found := configMap.Data["ca-certificate"] + if found { + s.Certificates.CaCertificateSecretKey = caCertificateSecretKey + logrus.Debugf("Settings::handleUpdateEvent: ca-certificate: %s", s.Certificates.CaCertificateSecretKey) + } else { + s.Certificates.CaCertificateSecretKey = "" + logrus.Warnf("Settings::handleUpdateEvent: ca-certificate key not found in ConfigMap") + } + + clientCertificateSecretKey, found := configMap.Data["client-certificate"] + if found { + s.Certificates.ClientCertificateSecretKey = clientCertificateSecretKey + logrus.Debugf("Settings::handleUpdateEvent: client-certificate: %s", s.Certificates.ClientCertificateSecretKey) + } else { + s.Certificates.ClientCertificateSecretKey = "" + logrus.Warnf("Settings::handleUpdateEvent: client-certificate key not found in ConfigMap") + } + + logrus.Debugf("Settings::handleUpdateEvent: \n\tHosts: %v,\n\tSettings: %v ", s.NginxPlusHosts, configMap) } func (s *Settings) parseHosts(hosts string) []string { @@ -255,3 +322,8 @@ func (s *Settings) parseHosts(hosts string) []string { func (s *Settings) updateHosts(hosts []string) { s.NginxPlusHosts = hosts } + +func isOurConfig(obj interface{}) (*corev1.ConfigMap, bool) { + configMap, ok := obj.(*corev1.ConfigMap) + return configMap, ok && configMap.Name == ConfigMapName && configMap.Namespace == ConfigMapsNamespace +} diff --git a/internal/synchronization/synchronizer.go b/internal/synchronization/synchronizer.go index 2f6c421..1061b01 100644 --- a/internal/synchronization/synchronizer.go +++ b/internal/synchronization/synchronizer.go @@ -107,7 +107,7 @@ func (s *Synchronizer) buildBorderClient(event *core.ServerUpdateEvent) (applica var err error - httpClient, err := communication.NewHttpClient() + httpClient, err := communication.NewHttpClient(s.settings) if err != nil { return nil, fmt.Errorf(`error creating HTTP client: %v`, err) } diff --git a/server-secret.yaml b/server-secret.yaml new file mode 100644 index 0000000..8299224 --- /dev/null +++ b/server-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVsekNDQTMrZ0F3SUJBZ0lVUFRrVExlS3FNQlRlNFZhQXpiL1hXcSt6THRBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eEVEQU9CZ05WQkFjTQpCMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0ZFc1cGRIa2dKaUJCCmJHeHBZVzVqWlhNd0hoY05Nak14TURBeU1qTXdPRFEyV2hjTk1qUXhNREF4TWpNd09EUTJXakI3TVFzd0NRWUQKVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLVjJGemFHbHVaM1J2YmpFUU1BNEdBMVVFQnd3SFUyVmhkSFJzWlRFTwpNQXdHQTFVRUNnd0ZUa2RKVGxneEhqQWNCZ05WQkFzTUZVTnZiVzExYm1sMGVTQW1JRUZzYkdsaGJtTmxjekVWCk1CTUdBMVVFQXd3TWJYbGtiMjFoYVc0dVkyOXRNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUIKQ2dLQ0FRRUF2RjJSVklsVEQvQWVSQkZSNTNrb1dlZmlhQ09keVJvWnJRSm04RGZJdk5vQ2pTdkNPUEhzZ2VSSQptcmhxcEhkc2FSK2RqSjNRbzFKemljeS9YNHgxRy9SSGRNM0d3OXNuTS9jdHBvb1FSZUtFTi90T1FnVnBMaEZQClE4K3cvaDVRSGZWWFFQcFdyeGhjTkFiK1hrbzk5SVBndE81blNvazExK2pEOGhJSVlTTUEvVElwUFBPeXNKdnkKOUhKUjBEY3pqWE1VQTR0dGphYTZ0cm8xNm81WEZsdDZPM251YTFkc0VLYVRJQmhWbXI4ZGJWVFd2c1VNYTV6WgpyR0pSa25Bc01RV1lxSVBBVG1MdXlaeHlYTG1WQkp4U1FzWTZqYVllLzcreXZGdUVCUTZ4MDByZ1Z0THpzWi9NCitZSHg2VUVXRFoxYzZuZWxxdlFja1FvSzdTd3VLUUlEQVFBQm80SUJLRENDQVNRd0NRWURWUjBUQkFJd0FEQUwKQmdOVkhROEVCQU1DQmVBd1NBWURWUjBSQkVFd1A0SU1iWGxrYjIxaGFXNHVZMjl0Z2hOelpYSjJaWEl1YlhsawpiMjFoYVc0dVkyOXRnZzRxTG0xNVpHOXRZV2x1TG1OdmJZY0VDZ0FBQ29jRUNnQUFDekFUQmdOVkhTVUVEREFLCkJnZ3JCZ0VGQlFjREFUQWRCZ05WSFE0RUZnUVVkOUVieTR0TDNZSjhqSTd6RzVKR2p5TW5vVDR3Z1lzR0ExVWQKSXdTQmd6Q0JnS0ZvcEdZd1pERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2xkaGMyaHBibWQwYjI0eApFREFPQmdOVkJBY01CMU5sWVhSMGJHVXhEakFNQmdOVkJBb01CVTVIU1U1WU1SNHdIQVlEVlFRTERCVkRiMjF0CmRXNXBkSGtnSmlCQmJHeHBZVzVqWlhPQ0ZGNGlKWWxnWFYrNksxclE0L1JacXEyTWxRTWZNQTBHQ1NxR1NJYjMKRFFFQkN3VUFBNElCQVFCRFY0OWtOanpCZmpPMjlNWVd1ZFlMNktzYlFrdlFDdzZ3dEF6cVJ2Smd5WEtBbXYzcwp6OFJjZHFNRTZ2bXdWZUxpS1ZDeDFSOXY1dlhBS0hMNmFSSi9QMk1QUm1Ic0pNM2MxSVF2NjAzVVYzaCtQL3JICll3QTVVRzBCMXlMaWs3N3RIdWZtRGFKdlRZMGpvcWJ0cVEwU1ByWjh6TGJQdmo3VTc1bStHWEx5VEp0UnRjdDUKSVJTblg0NlZxbWs0Q1l2dW1SaDJJTy8yUHpKNkpUbzA5VFpEOGpDbyttbHIzSXhXOHBsRDBYSkQ1YXY4cEQ2dApDZ2xnR2FyakRWSmVCQUZobkVIWStYelZDYzJVSktVblNiaDVWUVRLdno2dCtCWTlldi9LOFJGYlcxWVd3N1Q1CmQ0UzEyaC94cUpXcUJOdXFJeldkMTMvbTZaaHoraHorb0VvRQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzhYWkZVaVZNUDhCNUUKRVZIbmVTaFo1K0pvSTUzSkdobXRBbWJ3TjhpODJnS05LOEk0OGV5QjVFaWF1R3FrZDJ4cEg1Mk1uZENqVW5PSgp6TDlmakhVYjlFZDB6Y2JEMnljejl5Mm1paEJGNG9RMyswNUNCV2t1RVU5RHo3RCtIbEFkOVZkQStsYXZHRncwCkJ2NWVTajMwZytDMDdtZEtpVFhYNk1QeUVnaGhJd0Q5TWlrODg3S3dtL0wwY2xIUU56T05jeFFEaTIyTnBycTIKdWpYcWpsY1dXM283ZWU1clYyd1FwcE1nR0ZXYXZ4MXRWTmEreFF4cm5ObXNZbEdTY0N3eEJaaW9nOEJPWXU3SgpuSEpjdVpVRW5GSkN4anFOcGg3L3Y3SzhXNFFGRHJIVFN1Qlcwdk94bjh6NWdmSHBRUllOblZ6cWQ2V3E5QnlSCkNncnRMQzRwQWdNQkFBRUNnZ0VBQnRtRGh6dnhKeU5pV1FqNk0xSjVaYW81Z1BPZW5CUC9aVnV3MWtFVHVIa0QKQ1JMVGRCSlVEb3NqR3NFNXNONWVOUVVVZG1zU0RiRUVzNDVHL1VOVWRONUdyNWdyWEs3bzk0cExBTmlFd1UzUgo1TFBybU1TdFJQZ1YxaytaK09aWVNudnVudFhHUzRWZ0ZrenFJMlZNRXlBQ2pxeHhXWFFHTkdJL1VkdXNXS3FICkNlOWNGMXp0UGxBWkVIRU9JNTJEcVFZK3FhUUtNSjVIT3RNS1F6SzY5MGF0VUJmbWZxUCsxYTh4Vm1VV1cyaVgKNWpuN3IzeDRJdENwSlpwTHM2SzZRS29aNU9qd25nSTc4MkxuQ3V0cC9NZmg5RmVYSlFRRVppYXMzOW44c29NcwpCN0ZKQnpKalQwVlQxR2hVWnNwSUhra1AwR3pBQTd2Y3MwNitDQWc5Z1FLQmdRRG1CYTQ3MnlLYmwvZUIyY3BFCjdZeXVmaHk1M093OTFzNkkwYnQxYWZyajNvcHExV1VVWUJiOWhIUm1CRXkzVVE2V1JoenltdG1MdXBRTWh4enkKeThSMVFtNW1RUTI1T3lsNGl3TFZQdVE3Ty94dzh6UUdNR1pBY08zQ3NuYUM5Y3YxZFpnZzcvQ1BUTlpsY2RxZAp2OXFqNnpwVHFrT21OVnlFUGxPeFI3aHJ5UUtCZ1FEUm80WVNwcDRONHRlSWhIaG1uZGpYaVZkSDYyM3oxQldICnV1U3M0L0M2RDVjaUhWRTJYZnZ2cnQ4eEhBbEVycGtoaVFUZ1JzZHVwblVhVVFodUtaT1RNTXVGcDAzblA1cmMKUjlxY1MxQUVQUXZMVFNZN0dqaEptaExEVUM0WktGYmo4bXhkSWVBK09YL21mank4SFJTMWYxYncwR3owZCtvNQozVnlyOUU0ZllRS0JnUUNuY1QwckwxTGJCdDNTZFpMcmFDMC9uR2dXMkg1VWFia0JHZ09tN2hZSHFLa0VLZ0VoCnV1MGhjVGsyUml6K1NSQWdUanVtVXhqSHdYTWlSM3pJTlpMMmRQeGVqVDZMTjBqeUNlZHZDaEFrR24raVRUZnkKeFdxNXdEc2p2cnZNaTFjRWdLelVWVFc5YXdhcTVCMXJOZ3pYeEZVNk1EaDhsbDJabXJGYjNNU2dHUUtCZ0F1cQpmT2lHeXg3Y3M3L09GMkVtZ1kyay8rMXBwWW0vRUorbi85ZTdLNGMvSE5yeUpMWFF6eGRNZFBFbnJVQmNNdnRSCnc2cXpaWis3dGFLTVJkclRoM25XYWt6NnZYUVQ3d3M1R0dwQUtxakJ1T2xNVnNkTk16cXRUMFA5TDBPSklpUzMKTmQ2TTV3eXZhSFdzS3JjUkt6amFhRDBvYkJmQ29JOHR5VjFzVC9paEFvR0FWNHJzMU14d2VkRVVPTFo4Y0ZBQgpvYTE1MG41dVgwUGQ1Nm92enRXNHZoR2JyOVpjQ294dnR0a0VqQUNvTzdyanhBb1JyYVk1U1A0WTdHaHJxb043CkZYeFYrVVh0aGxyU21SQWUzS25ndzA4bkRpT0Fia0NZdFlGVGs3d3d3V0xYU2VYUWRCdUx1aWRqZ05PL2RhbkkKczUzVXU3OGlnRVdPa21YWThGYm1OVEU9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +kind: Secret +metadata: + creationTimestamp: null + name: nlk-tls-server-secret + namespace: nlk +type: kubernetes.io/tls