[{"data":1,"prerenderedAt":357},["ShallowReactive",2],{"\u002Fblog\u002Flabfromzero-1":3,"\u002Fblog\u002Flabfromzero-1-surround":346},{"id":4,"title":5,"authors":6,"badge":7,"body":9,"date":335,"description":336,"extension":337,"image":338,"meta":340,"navigation":341,"path":342,"seo":343,"stem":344,"__hash__":345},"posts\u002F1.blog\u002F3.LabFromZero-1\u002Findex.md","LabFromZero Part 1 - Hardware, PKI, Security",null,{"label":8},"Hardware, HSM, PKI, Security",{"type":10,"value":11,"toc":314},"minimark",[12,17,28,39,43,46,74,77,81,86,100,108,117,134,141,167,170,174,178,181,185,194,197,201,210,219,223,232,245,249,257,286,290,293,303,307],[13,14,16],"h2",{"id":15},"labfromzero-series-introduction","LabFromZero Series Introduction",[18,19,20],"blockquote",{},[21,22,23,27],"p",{},[24,25,26],"strong",{},"Series goal",": A Practical guide to construct a self-hosted lab and private-cloud environment with security as a first-class concern. Targeting the audience who is looking for a sovereign self managed infrastructure and platfrom as an alternative to public cloud environments",[21,29,30,31,38],{},"This series is based on the public repository ",[32,33,37],"a",{"href":34,"rel":35},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero",[36],"nofollow","LabFromZero",". The original material is organised as a collection of focused READMEs. This series is expanding it into a coherent blog series.",[13,40,42],{"id":41},"scope","Scope",[21,44,45],{},"A realistic on-premises platform that can host:",[47,48,49,53,56,59,62,65,68,71],"ul",{},[50,51,52],"li",{},"A private Kubernetes cluster (kubeadm, bare metal)",[50,54,55],{},"10GBit Ethernet Interconnect",[50,57,58],{},"Identity provider, OIDC (Zitadel)",[50,60,61],{},"Secrets management with hardware-backed unseal (OpenBao + YubiHSM)",[50,63,64],{},"GitOps (ForgeJo + FluxCD)",[50,66,67],{},"Observability (Prometheus + Grafana)",[50,69,70],{},"Gateway (Cilium + Gateway API + Envoy Gateway)",[50,72,73],{},"Supporting services (DNS, Headlamp, ODIC, etc.)",[21,75,76],{},"All of it sits behind a private PKI, full-disk encryption with TPM2 auto-unlock, and deliberate network segmentation.",[13,78,80],{"id":79},"hardware-bill-of-materials","Hardware Bill of Materials",[82,83,85],"h3",{"id":84},"network-edge","Network edge",[47,87,88,94,97],{},[50,89,90,93],{},[24,91,92],{},"Router",": MikroTik Clmoud Core Router CCR2004",[50,95,96],{},"10GBase-T RJ45 SFP+ to Ethernet Adapters",[50,98,99],{},"Patch cables, rack, UPS",[101,102],"img",{"alt":103,"className":104,"src":106,"width":107},"CPU",[105],"img-center","\u002Fblog\u002FLabFromZero\u002Frouter.webp",600,[82,109,111,112,116],{"id":110},"control-plane-node-control001","Control plane node (",[113,114,115],"code",{},"control001",")",[47,118,119,122,125,128,131],{},[50,120,121],{},"Lenovo ThinkCentre M75q Gen 5",[50,123,124],{},"CPU: AMD Ryzen 7 PRO 8700GE (8c\u002F16t)",[50,126,127],{},"RAM: 32 GB",[50,129,130],{},"SSD: 1 TB NVMe",[50,132,133],{},"Role: control-plane + system services (IDP, HSM connector, ForgeJo, monitoring, FluxCD, etc.)",[82,135,137,138,116],{"id":136},"worker-node-worker001","Worker node (",[113,139,140],{},"worker001",[47,142,143,146,149,152,155,158,161,164],{},[50,144,145],{},"Motherboard: ASRock TURIND8-2L2T",[50,147,148],{},"CPU: AMD EPYC 9354P (32c\u002F64t)",[50,150,151],{},"RAM: 256 GB (4×64 GB Kingston)",[50,153,154],{},"Storage: 5×2 TB Kingston Fury Renegade NVMe (ZFS RAID-Z2)",[50,156,157],{},"Cooler \u002F PSU \u002F case: Arctic Freezer 4U-SP5, Seasonic Focus GX-1000,",[50,159,160],{},"Case: 19\" rackable Silverstone RM52",[50,162,163],{},"Asrock TPM SPI module",[50,165,166],{},"Role: Local AI\u002FML workloads, Build agents, various cloud services",[21,168,169],{},"This is an intentionally modest setup with 1 control and 1 worker node setup. The control node is hosting the core platform services. The worker is a single high-core, high-mem machine. Scale-out or HA can be added \u002F hot-plugged dynamically later.",[101,171],{"alt":103,"className":172,"src":173,"width":107},[105],"\u002Fblog\u002FLabFromZero\u002Fcpu3.webp",[13,175,177],{"id":176},"security-considerations","Security Considerations",[21,179,180],{},"Nodes run Ubuntu Server 26.04 with Secure Boot, TPM 2.0 enabled and Microsoft UEFI KEK CAs enabled. Make sure that the BIOS is updated.",[82,182,184],{"id":183},"private-pki-with-offline-root-ca","Private PKI with offline Root CA",[21,186,187,188,193],{},"Trust for every TLS endpoint starts here. The Root CA key never touches the network, remains offline!\n",[32,189,192],{"href":190,"rel":191},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero\u002Fsrc\u002Fbranch\u002Fmaster\u002F1.0-pki-ca#readme",[36],"Generate the PKI Infrastrucutre","\nStore the PKI infra \u002F Root Keys securely on a veracrypt drive or KeepassXC attachment",[21,195,196],{},"The root ca certificate needs to be distributed and trusted on all nodes, later configurations rely on the proper trust chain.",[82,198,200],{"id":199},"intermediate-ca-and-service-certificates","Intermediate CA and Service Certificates",[21,202,203,204,209],{},"Cert-manager requires a ",[32,205,208],{"href":206,"rel":207},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero\u002Fsrc\u002Fbranch\u002Fmaster\u002F1.0-pki-ca#intermediace-ca-cert-manager",[36],"dedicated intermediate CA"," to issue gateway \u002F ingress certificates.",[21,211,212,213,218],{},"YubiHSM needs a ",[32,214,217],{"href":215,"rel":216},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero\u002Fsrc\u002Fbranch\u002Fmaster\u002F1.0-pki-ca#yubihsm-service-cert",[36],"dedicated TLS server"," cert and key to protect the connector endpoint.",[82,220,222],{"id":221},"full-disk-encryption-luks-tpm2-auto-unlock","Full-disk encryption (LUKS) + TPM2 auto-unlock",[21,224,225,226,231],{},"During node installation make sure to ",[32,227,230],{"href":228,"rel":229},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero\u002Fsrc\u002Fbranch\u002Fmaster\u002F1.2-k8s-nodes\u002FCONTROL_NODE.md#disk-layout",[36],"ecrypt the disk partitions",".",[47,233,234,237],{},[50,235,236],{},"Save the recovery passphrase into the offline \u002F break-glass secret store (veracrypt \u002F KeepassXC).",[50,238,239,244],{},[32,240,243],{"href":241,"rel":242},"https:\u002F\u002Fcodeberg.org\u002Ftessellative\u002FLabFromZero\u002Fsrc\u002Fbranch\u002Fmaster\u002F1.2-k8s-nodes\u002FCONTROL_NODE.md#luks-tpm2-auto-unlock",[36],"Setup the PCR 7 (Secure Boot state)"," which should be the practical default.",[82,246,248],{"id":247},"hardware-security-module-yubihsm-2","Hardware Security Module (YubiHSM 2)",[21,250,251,252,231],{},"Network accessible HSM service via ",[32,253,256],{"href":254,"rel":255},"https:\u002F\u002Fwww.yubico.com\u002Fch\u002Fproduct\u002Fyubihsm-2",[36],"Yubico's YubiHSM",[47,258,259,262,265,268,271,274,277,280,283],{},[50,260,261],{},"Hardware based strong security",[50,263,264],{},"Secure key storage and operations",[50,266,267],{},"Network shareable",[50,269,270],{},"Extensive cryptographic capabilities: RSA, ECC, ECDSA (ed25519), SHA-2, AES",[50,272,273],{},"M of N wrap key Backup and Restore",[50,275,276],{},"Interfaces via YubiHSM KSP, PKCS#11, and native libraries",[50,278,279],{},"Tamper evident Audit Logging",[50,281,282],{},"Secure Random Generation",[50,284,285],{},"Used for OpenBao seal-key operations so the unseal key stays secure and will not require manual unsealing.",[82,287,289],{"id":288},"offline-secret-store-breakglass-access","Offline secret store \u002F Breakglass access",[21,291,292],{},"KeePassXC (or equivalent) holds all break-glass credentials, CA private keys, HSM admin passwords, etc.\nExample store hierarchy",[294,295,300],"pre",{"className":296,"code":298,"language":299},[297],"language-text","Network\u002F\n  |- Router: Mikrotik - CRRF2004\nPKI\u002F\n  |- ROOT CA\n  |- Cert Manager Intermediate CA\nNodes\u002F\n  |- WORKER: bearmetal001\n  |- CONTROL: ctrl001\nIAM - Zitadel\u002F\n  |- admin @ LabFromZero.idp.labfromzero.internal\n  |- user @ LabFromZero.idp.labfromzero.internal\n  |- Zitadel OIDC App - Platform: k8s-labfrom0\n  |- Zitadel OIDC App - Platform: Headlamp\n  |- Zitadel OIDC App - Platform: OpenBao\nYubiHSM\u002F\n  |- admin-key\n  |- openabao-key\nServices\u002F\n  |- flux-deploy\n  |- openbao-root\n  |- k8s-image-puller-creds\n","text",[113,301,298],{"__ignoreMap":302},"",[82,304,306],{"id":305},"network-segmentation","Network segmentation",[21,308,309,310,313],{},"Dedicated lab subnet (",[113,311,312],{},"10.10.0.0\u002F16",") with clear static ranges for nodes, load-balancers, and dynamic hosts.",{"title":302,"searchDepth":315,"depth":315,"links":316},2,[317,318,319,327],{"id":15,"depth":315,"text":16},{"id":41,"depth":315,"text":42},{"id":79,"depth":315,"text":80,"children":320},[321,323,325],{"id":84,"depth":322,"text":85},3,{"id":110,"depth":322,"text":324},"Control plane node (control001)",{"id":136,"depth":322,"text":326},"Worker node (worker001)",{"id":176,"depth":315,"text":177,"children":328},[329,330,331,332,333,334],{"id":183,"depth":322,"text":184},{"id":199,"depth":322,"text":200},{"id":221,"depth":322,"text":222},{"id":247,"depth":322,"text":248},{"id":288,"depth":322,"text":289},{"id":305,"depth":322,"text":306},"2026-10-09","Foundations of a lab environment","md",{"src":339},"\u002Fblog\u002FLabFromZero\u002Fparts.webp",{},true,"\u002Fblog\u002Flabfromzero-1",{"title":5,"description":336},"1.blog\u002F3.LabFromZero-1\u002Findex","A0TzQ0toaiuI-tmkC1KXrXeqL9yCDFmxFo86xCYtmpQ",[347,352],{"title":348,"path":349,"stem":350,"description":351,"children":-1},"OpenBAO Auto Unseal with YubiHSM","\u002Fblog\u002Fopenbao-yubihsm","1.blog\u002F2.openbao-yubihsm\u002Findex","Proof of concept OpenBAO configuration with yubiHSM as the seal provider",{"title":353,"path":354,"stem":355,"description":356,"children":-1},"LabFromZero Part 2 - Network, k8s, HSM","\u002Fblog\u002Flabfromzero-2","1.blog\u002F4.LabFromZero-2\u002Findex","From network to cluster with HSM",1791905103952]