No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 
 
 

401 líneas
13 KiB

  1. { config, lib, pkgs, ... }:
  2. with lib;
  3. let
  4. top = config.services.kubernetes;
  5. cfg = top.pki;
  6. csrCA = pkgs.writeText "kube-pki-cacert-csr.json" (builtins.toJSON {
  7. key = {
  8. algo = "rsa";
  9. size = 2048;
  10. };
  11. names = singleton cfg.caSpec;
  12. });
  13. csrCfssl = pkgs.writeText "kube-pki-cfssl-csr.json" (builtins.toJSON {
  14. key = {
  15. algo = "rsa";
  16. size = 2048;
  17. };
  18. CN = top.masterAddress;
  19. hosts = cfg.cfsslAPIExtraSANs;
  20. });
  21. cfsslAPITokenBaseName = "apitoken.secret";
  22. cfsslAPITokenPath = "${config.services.cfssl.dataDir}/${cfsslAPITokenBaseName}";
  23. certmgrAPITokenPath = "${top.secretsPath}/${cfsslAPITokenBaseName}";
  24. cfsslAPITokenLength = 32;
  25. clusterAdminKubeconfig = with cfg.certs.clusterAdmin;
  26. top.lib.mkKubeConfig "cluster-admin" {
  27. server = top.apiserverAddress;
  28. certFile = cert;
  29. keyFile = key;
  30. };
  31. remote = with config.services; "https://${kubernetes.masterAddress}:${toString cfssl.port}";
  32. in
  33. {
  34. ###### interface
  35. options.services.kubernetes.pki = with lib.types; {
  36. enable = mkEnableOption "easyCert issuer service";
  37. certs = mkOption {
  38. description = "List of certificate specs to feed to cert generator.";
  39. default = {};
  40. type = attrs;
  41. };
  42. genCfsslCACert = mkOption {
  43. description = ''
  44. Whether to automatically generate cfssl CA certificate and key,
  45. if they don't exist.
  46. '';
  47. default = true;
  48. type = bool;
  49. };
  50. genCfsslAPICerts = mkOption {
  51. description = ''
  52. Whether to automatically generate cfssl API webserver TLS cert and key,
  53. if they don't exist.
  54. '';
  55. default = true;
  56. type = bool;
  57. };
  58. cfsslAPIExtraSANs = mkOption {
  59. description = ''
  60. Extra x509 Subject Alternative Names to be added to the cfssl API webserver TLS cert.
  61. '';
  62. default = [];
  63. example = [ "subdomain.example.com" ];
  64. type = listOf str;
  65. };
  66. genCfsslAPIToken = mkOption {
  67. description = ''
  68. Whether to automatically generate cfssl API-token secret,
  69. if they doesn't exist.
  70. '';
  71. default = true;
  72. type = bool;
  73. };
  74. pkiTrustOnBootstrap = mkOption {
  75. description = "Whether to always trust remote cfssl server upon initial PKI bootstrap.";
  76. default = true;
  77. type = bool;
  78. };
  79. caCertPathPrefix = mkOption {
  80. description = ''
  81. Path-prefrix for the CA-certificate to be used for cfssl signing.
  82. Suffixes ".pem" and "-key.pem" will be automatically appended for
  83. the public and private keys respectively.
  84. '';
  85. default = "${config.services.cfssl.dataDir}/ca";
  86. type = str;
  87. };
  88. caSpec = mkOption {
  89. description = "Certificate specification for the auto-generated CAcert.";
  90. default = {
  91. CN = "kubernetes-cluster-ca";
  92. O = "NixOS";
  93. OU = "services.kubernetes.pki.caSpec";
  94. L = "auto-generated";
  95. };
  96. type = attrs;
  97. };
  98. etcClusterAdminKubeconfig = mkOption {
  99. description = ''
  100. Symlink a kubeconfig with cluster-admin privileges to environment path
  101. (/etc/<path>).
  102. '';
  103. default = null;
  104. type = nullOr str;
  105. };
  106. };
  107. ###### implementation
  108. config = mkIf cfg.enable
  109. (let
  110. cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
  111. cfsslCert = "${cfsslCertPathPrefix}.pem";
  112. cfsslKey = "${cfsslCertPathPrefix}-key.pem";
  113. in
  114. {
  115. services.cfssl = mkIf (top.apiserver.enable) {
  116. enable = true;
  117. address = "0.0.0.0";
  118. tlsCert = cfsslCert;
  119. tlsKey = cfsslKey;
  120. configFile = toString (pkgs.writeText "cfssl-config.json" (builtins.toJSON {
  121. signing = {
  122. profiles = {
  123. default = {
  124. usages = ["digital signature"];
  125. auth_key = "default";
  126. expiry = "720h";
  127. };
  128. };
  129. };
  130. auth_keys = {
  131. default = {
  132. type = "standard";
  133. key = "file:${cfsslAPITokenPath}";
  134. };
  135. };
  136. }));
  137. };
  138. systemd.services.cfssl.preStart = with pkgs; with config.services.cfssl; mkIf (top.apiserver.enable)
  139. (concatStringsSep "\n" [
  140. "set -e"
  141. (optionalString cfg.genCfsslCACert ''
  142. if [ ! -f "${cfg.caCertPathPrefix}.pem" ]; then
  143. ${cfssl}/bin/cfssl genkey -initca ${csrCA} | \
  144. ${cfssl}/bin/cfssljson -bare ${cfg.caCertPathPrefix}
  145. fi
  146. '')
  147. (optionalString cfg.genCfsslAPICerts ''
  148. if [ ! -f "${dataDir}/cfssl.pem" ]; then
  149. ${cfssl}/bin/cfssl gencert -ca "${cfg.caCertPathPrefix}.pem" -ca-key "${cfg.caCertPathPrefix}-key.pem" ${csrCfssl} | \
  150. ${cfssl}/bin/cfssljson -bare ${cfsslCertPathPrefix}
  151. fi
  152. '')
  153. (optionalString cfg.genCfsslAPIToken ''
  154. if [ ! -f "${cfsslAPITokenPath}" ]; then
  155. head -c ${toString (cfsslAPITokenLength / 2)} /dev/urandom | od -An -t x | tr -d ' ' >"${cfsslAPITokenPath}"
  156. fi
  157. chown cfssl "${cfsslAPITokenPath}" && chmod 400 "${cfsslAPITokenPath}"
  158. '')]);
  159. systemd.services.kube-certmgr-bootstrap = {
  160. description = "Kubernetes certmgr bootstrapper";
  161. wantedBy = [ "certmgr.service" ];
  162. after = [ "cfssl.target" ];
  163. script = concatStringsSep "\n" [''
  164. set -e
  165. # If there's a cfssl (cert issuer) running locally, then don't rely on user to
  166. # manually paste it in place. Just symlink.
  167. # otherwise, create the target file, ready for users to insert the token
  168. if [ -f "${cfsslAPITokenPath}" ]; then
  169. ln -fs "${cfsslAPITokenPath}" "${certmgrAPITokenPath}"
  170. else
  171. touch "${certmgrAPITokenPath}" && chmod 600 "${certmgrAPITokenPath}"
  172. fi
  173. ''
  174. (optionalString (cfg.pkiTrustOnBootstrap) ''
  175. if [ ! -f "${top.caFile}" ] || [ $(cat "${top.caFile}" | wc -c) -lt 1 ]; then
  176. ${pkgs.curl}/bin/curl --fail-early -f -kd '{}' ${remote}/api/v1/cfssl/info | \
  177. ${pkgs.cfssl}/bin/cfssljson -stdout >${top.caFile}
  178. fi
  179. '')
  180. ];
  181. serviceConfig = {
  182. RestartSec = "10s";
  183. Restart = "on-failure";
  184. };
  185. };
  186. services.certmgr = {
  187. enable = true;
  188. package = pkgs.certmgr-selfsigned;
  189. svcManager = "command";
  190. specs =
  191. let
  192. mkSpec = _: cert: {
  193. inherit (cert) action;
  194. authority = {
  195. inherit remote;
  196. file.path = cert.caCert;
  197. root_ca = cert.caCert;
  198. profile = "default";
  199. auth_key_file = certmgrAPITokenPath;
  200. };
  201. certificate = {
  202. path = cert.cert;
  203. };
  204. private_key = cert.privateKeyOptions;
  205. request = {
  206. inherit (cert) CN hosts;
  207. key = {
  208. algo = "rsa";
  209. size = 2048;
  210. };
  211. names = [ cert.fields ];
  212. };
  213. };
  214. in
  215. mapAttrs mkSpec cfg.certs;
  216. };
  217. #TODO: Get rid of kube-addon-manager in the future for the following reasons
  218. # - it is basically just a shell script wrapped around kubectl
  219. # - it assumes that it is clusterAdmin or can gain clusterAdmin rights through serviceAccount
  220. # - it is designed to be used with k8s system components only
  221. # - it would be better with a more Nix-oriented way of managing addons
  222. systemd.services.kube-addon-manager = mkIf top.addonManager.enable (mkMerge [{
  223. environment.KUBECONFIG = with cfg.certs.addonManager;
  224. top.lib.mkKubeConfig "addon-manager" {
  225. server = top.apiserverAddress;
  226. certFile = cert;
  227. keyFile = key;
  228. };
  229. }
  230. (optionalAttrs (top.addonManager.bootstrapAddons != {}) {
  231. serviceConfig.PermissionsStartOnly = true;
  232. preStart = with pkgs;
  233. let
  234. files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
  235. top.addonManager.bootstrapAddons;
  236. in
  237. ''
  238. export KUBECONFIG=${clusterAdminKubeconfig}
  239. ${kubectl}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
  240. '';
  241. })]);
  242. environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
  243. clusterAdminKubeconfig;
  244. environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
  245. (pkgs.writeScriptBin "nixos-kubernetes-node-join" ''
  246. set -e
  247. exec 1>&2
  248. if [ $# -gt 0 ]; then
  249. echo "Usage: $(basename $0)"
  250. echo ""
  251. echo "No args. Apitoken must be provided on stdin."
  252. echo "To get the apitoken, execute: 'sudo cat ${certmgrAPITokenPath}' on the master node."
  253. exit 1
  254. fi
  255. if [ $(id -u) != 0 ]; then
  256. echo "Run as root please."
  257. exit 1
  258. fi
  259. read -r token
  260. if [ ''${#token} != ${toString cfsslAPITokenLength} ]; then
  261. echo "Token must be of length ${toString cfsslAPITokenLength}."
  262. exit 1
  263. fi
  264. echo $token > ${certmgrAPITokenPath}
  265. chmod 600 ${certmgrAPITokenPath}
  266. echo "Restarting certmgr..." >&1
  267. systemctl restart certmgr
  268. echo "Waiting for certs to appear..." >&1
  269. ${optionalString top.kubelet.enable ''
  270. while [ ! -f ${cfg.certs.kubelet.cert} ]; do sleep 1; done
  271. echo "Restarting kubelet..." >&1
  272. systemctl restart kubelet
  273. ''}
  274. ${optionalString top.proxy.enable ''
  275. while [ ! -f ${cfg.certs.kubeProxyClient.cert} ]; do sleep 1; done
  276. echo "Restarting kube-proxy..." >&1
  277. systemctl restart kube-proxy
  278. ''}
  279. ${optionalString top.flannel.enable ''
  280. while [ ! -f ${cfg.certs.flannelClient.cert} ]; do sleep 1; done
  281. echo "Restarting flannel..." >&1
  282. systemctl restart flannel
  283. ''}
  284. echo "Node joined succesfully"
  285. '')];
  286. # isolate etcd on loopback at the master node
  287. # easyCerts doesn't support multimaster clusters anyway atm.
  288. services.etcd = with cfg.certs.etcd; {
  289. listenClientUrls = ["https://127.0.0.1:2379"];
  290. listenPeerUrls = ["https://127.0.0.1:2380"];
  291. advertiseClientUrls = ["https://etcd.local:2379"];
  292. initialCluster = ["${top.masterAddress}=https://etcd.local:2380"];
  293. initialAdvertisePeerUrls = ["https://etcd.local:2380"];
  294. certFile = mkDefault cert;
  295. keyFile = mkDefault key;
  296. trustedCaFile = mkDefault caCert;
  297. };
  298. networking.extraHosts = mkIf (config.services.etcd.enable) ''
  299. 127.0.0.1 etcd.${top.addons.dns.clusterDomain} etcd.local
  300. '';
  301. services.flannel = with cfg.certs.flannelClient; {
  302. kubeconfig = top.lib.mkKubeConfig "flannel" {
  303. server = top.apiserverAddress;
  304. certFile = cert;
  305. keyFile = key;
  306. };
  307. };
  308. services.kubernetes = {
  309. apiserver = mkIf top.apiserver.enable (with cfg.certs.apiServer; {
  310. etcd = with cfg.certs.apiserverEtcdClient; {
  311. servers = ["https://etcd.local:2379"];
  312. certFile = mkDefault cert;
  313. keyFile = mkDefault key;
  314. caFile = mkDefault caCert;
  315. };
  316. clientCaFile = mkDefault caCert;
  317. tlsCertFile = mkDefault cert;
  318. tlsKeyFile = mkDefault key;
  319. serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.cert;
  320. kubeletClientCaFile = mkDefault caCert;
  321. kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert;
  322. kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key;
  323. proxyClientCertFile = mkDefault cfg.certs.apiserverProxyClient.cert;
  324. proxyClientKeyFile = mkDefault cfg.certs.apiserverProxyClient.key;
  325. });
  326. controllerManager = mkIf top.controllerManager.enable {
  327. serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.key;
  328. rootCaFile = cfg.certs.controllerManagerClient.caCert;
  329. kubeconfig = with cfg.certs.controllerManagerClient; {
  330. certFile = mkDefault cert;
  331. keyFile = mkDefault key;
  332. };
  333. };
  334. scheduler = mkIf top.scheduler.enable {
  335. kubeconfig = with cfg.certs.schedulerClient; {
  336. certFile = mkDefault cert;
  337. keyFile = mkDefault key;
  338. };
  339. };
  340. kubelet = mkIf top.kubelet.enable {
  341. clientCaFile = mkDefault cfg.certs.kubelet.caCert;
  342. tlsCertFile = mkDefault cfg.certs.kubelet.cert;
  343. tlsKeyFile = mkDefault cfg.certs.kubelet.key;
  344. kubeconfig = with cfg.certs.kubeletClient; {
  345. certFile = mkDefault cert;
  346. keyFile = mkDefault key;
  347. };
  348. };
  349. proxy = mkIf top.proxy.enable {
  350. kubeconfig = with cfg.certs.kubeProxyClient; {
  351. certFile = mkDefault cert;
  352. keyFile = mkDefault key;
  353. };
  354. };
  355. };
  356. });
  357. }