| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 1 | // Copyright 2020 The Monogon Project Authors. |
| 2 | // |
| 3 | // SPDX-License-Identifier: Apache-2.0 |
| 4 | // |
| 5 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | // you may not use this file except in compliance with the License. |
| 7 | // You may obtain a copy of the License at |
| 8 | // |
| 9 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | // |
| 11 | // Unless required by applicable law or agreed to in writing, software |
| 12 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | // See the License for the specific language governing permissions and |
| 15 | // limitations under the License. |
| 16 | |
| 17 | package kubernetes |
| 18 | |
| 19 | import ( |
| 20 | "context" |
| Lorenz Brun | 6211e4d | 2023-11-14 19:09:40 +0100 | [diff] [blame] | 21 | "encoding/json" |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 22 | "encoding/pem" |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 23 | "fmt" |
| 24 | "net" |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 25 | "os/exec" |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 26 | |
| Lorenz Brun | 6211e4d | 2023-11-14 19:09:40 +0100 | [diff] [blame] | 27 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 28 | "k8s.io/apimachinery/pkg/runtime" |
| 29 | "k8s.io/apimachinery/pkg/runtime/schema" |
| 30 | "k8s.io/apiserver/pkg/apis/apiserver" |
| 31 | "k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity" |
| 32 | podsecurityadmissionv1 "k8s.io/pod-security-admission/admission/api/v1" |
| 33 | |
| Serge Bazanski | 31370b0 | 2021-01-07 16:31:14 +0100 | [diff] [blame] | 34 | common "source.monogon.dev/metropolis/node" |
| 35 | "source.monogon.dev/metropolis/node/core/localstorage" |
| 36 | "source.monogon.dev/metropolis/node/kubernetes/pki" |
| Tim Windelschmidt | 9f21f53 | 2024-05-07 15:14:20 +0200 | [diff] [blame] | 37 | "source.monogon.dev/osbase/fileargs" |
| 38 | "source.monogon.dev/osbase/supervisor" |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 39 | ) |
| 40 | |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 41 | type apiserverService struct { |
| Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 42 | KPKI *pki.PKI |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 43 | AdvertiseAddress net.IP |
| 44 | ServiceIPRange net.IPNet |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 45 | EphemeralConsensusDirectory *localstorage.EphemeralConsensusDirectory |
| 46 | |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 47 | // All PKI-related things are in DER |
| 48 | idCA []byte |
| 49 | kubeletClientCert []byte |
| 50 | kubeletClientKey []byte |
| 51 | aggregationCA []byte |
| 52 | aggregationClientCert []byte |
| 53 | aggregationClientKey []byte |
| 54 | serviceAccountPrivKey []byte // In PKIX form |
| 55 | serverCert []byte |
| 56 | serverKey []byte |
| 57 | } |
| 58 | |
| Lorenz Brun | 6211e4d | 2023-11-14 19:09:40 +0100 | [diff] [blame] | 59 | func mustWrapUnknownJSON(o schema.ObjectKind) *runtime.Unknown { |
| 60 | oRaw, err := json.Marshal(o) |
| 61 | if err != nil { |
| 62 | panic("While marshaling object into runtime.Unknown: " + err.Error()) |
| 63 | } |
| 64 | var typ runtime.TypeMeta |
| 65 | typ.SetGroupVersionKind(o.GroupVersionKind()) |
| 66 | return &runtime.Unknown{ |
| 67 | TypeMeta: typ, |
| 68 | Raw: oRaw, |
| 69 | ContentType: runtime.ContentTypeJSON, |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | func mustMarshalJSON(o any) []byte { |
| 74 | out, err := json.Marshal(o) |
| 75 | if err != nil { |
| 76 | panic("mustMarshalJSON failed: " + err.Error()) |
| 77 | } |
| 78 | return out |
| 79 | } |
| 80 | |
| 81 | var ( |
| 82 | podsecurityadmission = &podsecurityadmissionv1.PodSecurityConfiguration{ |
| 83 | TypeMeta: metav1.TypeMeta{ |
| 84 | APIVersion: podsecurityadmissionv1.SchemeGroupVersion.String(), |
| 85 | Kind: "PodSecurityConfiguration", |
| 86 | }, |
| 87 | Defaults: podsecurityadmissionv1.PodSecurityDefaults{ |
| 88 | Enforce: "baseline", |
| 89 | Warn: "baseline", |
| 90 | Audit: "baseline", |
| 91 | }, |
| 92 | Exemptions: podsecurityadmissionv1.PodSecurityExemptions{}, |
| 93 | } |
| 94 | |
| 95 | admissionConfig = apiserver.AdmissionConfiguration{ |
| 96 | TypeMeta: metav1.TypeMeta{ |
| 97 | APIVersion: apiserver.SchemeGroupVersion.String(), |
| 98 | Kind: "AdmissionConfiguration", |
| 99 | }, |
| 100 | Plugins: []apiserver.AdmissionPluginConfiguration{{ |
| 101 | Name: podsecurity.PluginName, |
| 102 | Configuration: mustWrapUnknownJSON(podsecurityadmission), |
| 103 | }}, |
| 104 | } |
| 105 | |
| 106 | admissionConfigRaw = mustMarshalJSON(admissionConfig) |
| 107 | ) |
| 108 | |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 109 | func (s *apiserverService) loadPKI(ctx context.Context) error { |
| Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame] | 110 | for _, el := range []struct { |
| 111 | targetCert *[]byte |
| 112 | targetKey *[]byte |
| 113 | name pki.KubeCertificateName |
| 114 | }{ |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 115 | {&s.idCA, nil, pki.IdCA}, |
| Serge Bazanski | 9411f7c | 2021-03-10 13:12:53 +0100 | [diff] [blame] | 116 | {&s.kubeletClientCert, &s.kubeletClientKey, pki.APIServerKubeletClient}, |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 117 | {&s.aggregationCA, nil, pki.AggregationCA}, |
| 118 | {&s.aggregationClientCert, &s.aggregationClientKey, pki.FrontProxyClient}, |
| 119 | {&s.serverCert, &s.serverKey, pki.APIServer}, |
| Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame] | 120 | } { |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 121 | cert, key, err := s.KPKI.Certificate(ctx, el.name) |
| Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame] | 122 | if err != nil { |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 123 | return fmt.Errorf("could not load certificate %q from PKI: %w", el.name, err) |
| Serge Bazanski | dbfc638 | 2020-06-19 20:35:43 +0200 | [diff] [blame] | 124 | } |
| 125 | if el.targetCert != nil { |
| 126 | *el.targetCert = cert |
| 127 | } |
| 128 | if el.targetKey != nil { |
| 129 | *el.targetKey = key |
| 130 | } |
| 131 | } |
| 132 | |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 133 | var err error |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 134 | s.serviceAccountPrivKey, err = s.KPKI.ServiceAccountKey(ctx) |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 135 | if err != nil { |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 136 | return fmt.Errorf("could not load serviceaccount privkey: %w", err) |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 137 | } |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 138 | return nil |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 139 | } |
| 140 | |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 141 | func (s *apiserverService) Run(ctx context.Context) error { |
| 142 | if err := s.loadPKI(ctx); err != nil { |
| 143 | return fmt.Errorf("loading PKI data failed: %w", err) |
| 144 | } |
| 145 | args, err := fileargs.New() |
| 146 | if err != nil { |
| 147 | panic(err) // If this fails, something is very wrong. Just crash. |
| 148 | } |
| 149 | defer args.Close() |
| 150 | |
| 151 | cmd := exec.CommandContext(ctx, "/kubernetes/bin/kube", "kube-apiserver", |
| 152 | fmt.Sprintf("--advertise-address=%v", s.AdvertiseAddress.String()), |
| 153 | "--authorization-mode=Node,RBAC", |
| 154 | args.FileOpt("--client-ca-file", "client-ca.pem", |
| 155 | pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.idCA})), |
| Lorenz Brun | 6211e4d | 2023-11-14 19:09:40 +0100 | [diff] [blame] | 156 | "--enable-admission-plugins=NodeRestriction", |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 157 | "--enable-aggregator-routing=true", |
| Serge Bazanski | 52304a8 | 2021-10-29 16:56:18 +0200 | [diff] [blame] | 158 | fmt.Sprintf("--secure-port=%d", common.KubernetesAPIPort), |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 159 | fmt.Sprintf("--etcd-servers=unix:///%s:0", s.EphemeralConsensusDirectory.ClientSocket.FullPath()), |
| 160 | args.FileOpt("--kubelet-client-certificate", "kubelet-client-cert.pem", |
| 161 | pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.kubeletClientCert})), |
| 162 | args.FileOpt("--kubelet-client-key", "kubelet-client-key.pem", |
| 163 | pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.kubeletClientKey})), |
| 164 | "--kubelet-preferred-address-types=InternalIP", |
| 165 | args.FileOpt("--proxy-client-cert-file", "aggregation-client-cert.pem", |
| 166 | pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.aggregationClientCert})), |
| 167 | args.FileOpt("--proxy-client-key-file", "aggregation-client-key.pem", |
| 168 | pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.aggregationClientKey})), |
| Lorenz Brun | cc078df | 2021-12-23 11:51:55 +0100 | [diff] [blame] | 169 | "--requestheader-allowed-names=front-proxy-client,metropolis-auth-proxy-client", |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 170 | args.FileOpt("--requestheader-client-ca-file", "aggregation-ca.pem", |
| 171 | pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.aggregationCA})), |
| 172 | "--requestheader-extra-headers-prefix=X-Remote-Extra-", |
| 173 | "--requestheader-group-headers=X-Remote-Group", |
| 174 | "--requestheader-username-headers=X-Remote-User", |
| 175 | args.FileOpt("--service-account-key-file", "service-account-pubkey.pem", |
| 176 | pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.serviceAccountPrivKey})), |
| Lorenz Brun | d13c1c6 | 2022-03-30 19:58:58 +0200 | [diff] [blame] | 177 | args.FileOpt("--service-account-signing-key-file", "service-account-signing-key.pem", |
| 178 | pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.serviceAccountPrivKey})), |
| 179 | "--service-account-issuer", "https://metropolis.internal", // TODO: Figure out federation |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 180 | fmt.Sprintf("--service-cluster-ip-range=%v", s.ServiceIPRange.String()), |
| Tim Windelschmidt | 0300077 | 2023-07-03 02:19:28 +0200 | [diff] [blame] | 181 | // We use a patch for the allocator that prevents usage of system ports. |
| Tim Windelschmidt | 92316fd | 2024-04-18 23:06:40 +0200 | [diff] [blame] | 182 | "--service-node-port-range=1-65535", |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 183 | args.FileOpt("--tls-cert-file", "server-cert.pem", |
| 184 | pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: s.serverCert})), |
| 185 | args.FileOpt("--tls-private-key-file", "server-key.pem", |
| 186 | pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: s.serverKey})), |
| Lorenz Brun | 6211e4d | 2023-11-14 19:09:40 +0100 | [diff] [blame] | 187 | args.FileOpt("--admission-control-config-file", "admission-control.json", admissionConfigRaw), |
| Serge Bazanski | c2c7ad9 | 2020-07-13 17:20:09 +0200 | [diff] [blame] | 188 | ) |
| 189 | if args.Error() != nil { |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 190 | return err |
| 191 | } |
| Serge Bazanski | 0560429 | 2021-03-12 17:47:21 +0100 | [diff] [blame] | 192 | return supervisor.RunCommand(ctx, cmd, supervisor.ParseKLog()) |
| Lorenz Brun | 6e8f69c | 2019-11-18 10:44:24 +0100 | [diff] [blame] | 193 | } |