Skip to content

Commit 065082e

Browse files
committed
feat: replace static iOS cert embedding with SCEP for on-device key generation
iOS/macOS profiles now use a SCEP payload instead of a bundled PKCS#12. PINT acts as a SCEP Registration Authority: it issues a one-time challenge at profile download time, validates it when the device submits its CSR, and proxies the signing request to FreeIPA. iOS handles automatic renewal before the 1-year pint_wifi cert expires, so users install the profile once and never touch it again. - Add internal/scep: ChallengeStore (15-min one-time tokens), RA cert generation/parsing (self-signed RSA 2048, never expires), and SCEP HTTP handler (GetCACaps, GetCACert, PKIOperation with GET fallback) - Replace PKCS#12 identity payload in mobileconfig with com.apple.security.scep - Revert iOS cert path in main.go back to EC marshaling for RadSec/signing certs - Add Caddy to Procfile (only starts when PINT_SERVER_URL=https://localhost:8443) - Wire PINT_SCEP_RA_CERT_SECRET through Helm chart (helper, values, deployment, role) - Update pint_wifi profile: 1-year validity, accept RSA and EC keys - Update profile page: "1 year" validity, Manual Configuration section for Android/Windows/Linux, collapsible SCEP explainer for iOS/macOS - Add date to dev log timestamps - Update README: dual cert-gen paths with sequence diagrams, SCEP RA notes, Testing SCEP Locally section
1 parent 4723bce commit 065082e

19 files changed

Lines changed: 689 additions & 94 deletions

File tree

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pint: set -a && . .env.dev && set +a && until nc -z localhost 8088; do sleep 0.2; done && ./pint
22
ipa-stub: set -a && . .env.dev && set +a && ./freeipa-stub
3+
caddy: set -a && . .env.dev && set +a && if [ "$PINT_SERVER_URL" = "https://localhost:8443" ]; then caddy reverse-proxy --from https://localhost:8443 --to http://localhost:8080; else sleep infinity; fi

README.md

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,65 @@ flowchart TD
2323

2424
### Certificate Generation via FreeIPA
2525

26-
Every certificate PINT issues follows the same path: generate a secp384r1 ECDSA keypair locally, build a CSR, and call FreeIPA's `cert_request` RPC with the appropriate CA and profile. FreeIPA's Dogtag CA signs the cert and returns the DER-encoded result. The private key never leaves PINT; it is either bundled into the download or shown once and discarded.
26+
PINT uses two distinct paths to issue WiFi client certificates depending on the platform.
27+
28+
**iOS and macOS — SCEP (on-device key generation)**
29+
30+
PINT acts as a SCEP Registration Authority. The device generates its own RSA 2048 keypair locally and enrolls through the `/scep` endpoint embedded in the mobileconfig. The private key never leaves the device, and iOS handles automatic renewal before the certificate expires.
31+
32+
```mermaid
33+
sequenceDiagram
34+
participant iOS as iOS / macOS Device
35+
participant P as PINT
36+
participant IPA as FreeIPA (Dogtag)
37+
38+
iOS->>P: Download profile (authenticated)
39+
P->>P: Issue one-time SCEP challenge
40+
P-->>iOS: Mobileconfig with SCEP payload + challenge
41+
note over iOS: Profile installed; device generates RSA 2048 keypair
42+
iOS->>P: SCEP PKIOperation (CSR + challenge, CMS-wrapped)
43+
P->>P: Validate challenge; extract username
44+
P->>IPA: cert_request(CSR, username, pint_wifi)
45+
IPA-->>P: Signed certificate (DER)
46+
P-->>iOS: CMS-wrapped certificate response
47+
note over iOS: Certificate installed; WiFi connects automatically
48+
note over iOS,P: Near expiry: iOS re-runs SCEP automatically
49+
```
50+
51+
The SCEP Registration Authority (RA) uses a self-signed RSA 2048 certificate (CN `CSH PINT SCEP RA`) stored in the `pint-scep-ra-cert` Kubernetes Secret. PINT generates this automatically on first startup if the Secret does not exist. It is intentionally self-signed and set to never expire. The RA cert is not a trust anchor; it is only used to encrypt the CMS envelope during the SCEP exchange. iOS identifies it via the SHA-1 fingerprint embedded in the mobileconfig's SCEP payload.
52+
53+
**All other platforms — server-side key generation**
54+
55+
For Android, Windows, and Linux, PINT generates a secp384r1 ECDSA keypair, submits the CSR to FreeIPA's `cert_request` RPC, and bundles the result into a PKCS#12 archive for download. The private key is shown once and never stored.
2756

2857
```mermaid
2958
sequenceDiagram
30-
participant Client as Browser / CLI
59+
participant Client as Browser
3160
participant P as PINT
3261
participant IPA as FreeIPA (Dogtag)
3362
34-
Client->>P: Request enrollment
63+
Client->>P: Request profile download
3564
P->>P: Generate secp384r1 keypair + CSR
3665
P->>IPA: cert_request(CSR, principal, CA, profile)
3766
IPA-->>P: Signed certificate (DER)
38-
P->>P: Bundle into profile / p12 / PEM
67+
P->>P: Bundle into .p12 / .xml
3968
P-->>Client: Download
4069
```
4170

4271
PINT authenticates to FreeIPA using a service account specified by `PINT_IPA_SERVICE_ACCOUNT` and `PINT_IPA_PASSWORD`. The session is established at startup and re-authenticated automatically on 401.
4372

4473
#### Profiles
4574

46-
Three custom Dogtag certificate profiles control validity, key usage, and subject enforcement. All profiles force `O=CSH.RIT.EDU` in the issued certificate subject regardless of what the CSR contains, and all require secp384r1 EC keys.
75+
Three custom Dogtag certificate profiles control validity, key usage, and subject enforcement. All profiles force `O=CSH.RIT.EDU` in the issued certificate subject regardless of what the CSR contains.
4776

48-
| Profile | Purpose | Validity | EKU |
49-
|---|---|---|---|
50-
| `pint_wifi` | EAP-TLS client certs for member devices | 5 years | `clientAuth` |
51-
| `pint_radsec_client` | mTLS client certs for WiFi controllers | 5 years | `clientAuth` |
52-
| `pint_radsec_server` | mTLS server cert for the FreeRADIUS RadSec listener | 90 days | `serverAuth` |
53-
| `pint_profile_signing` | CMS signing cert for iOS mobileconfig profiles | 1 year | `codeSigning` |
77+
| Profile | Purpose | Validity | Key | EKU |
78+
|---|---|---|---|---|
79+
| `pint_wifi` | EAP-TLS client certs for member devices | 1 year | RSA 2048 or EC | `clientAuth` |
80+
| `pint_radsec_client` | mTLS client certs for WiFi controllers | 5 years | EC (secp384r1) | `clientAuth` |
81+
| `pint_radsec_server` | mTLS server cert for the FreeRADIUS RadSec listener | 90 days | EC (secp384r1) | `serverAuth` |
82+
| `pint_profile_signing` | CMS signing cert for iOS mobileconfig profiles | 1 year | EC (secp384r1) | `codeSigning` |
5483

55-
Five-year validity on client certs minimises re-enrollment burden. The 90-day server cert and 1-year profile signing cert are automatically renewed by PINT (see [RadSec Server Cert](#radsec-server-cert) and [Profile Signing Cert](#profile-signing-cert)).
84+
The `pint_wifi` profile accepts both RSA and EC keys because iOS generates RSA 2048 on-device via SCEP while other platforms submit EC keypairs generated by PINT. The 1-year validity is short enough to rotate credentials regularly while remaining transparent to users on iOS/macOS, where SCEP handles renewal automatically. The 90-day server cert and 1-year profile signing cert are also automatically renewed by PINT (see [RadSec Server Cert](#radsec-server-cert) and [Profile Signing Cert](#profile-signing-cert)).
5685

5786
Profile config files live in `ipa/profiles/`. They must be imported into FreeIPA once before PINT can use them. Use `ipa/update_profile.py`, which supports three actions:
5887

@@ -82,11 +111,11 @@ Members visit `/profile` and download a platform-specific package. PINT issues a
82111

83112
| Platform | Output | Contents |
84113
|---|---|---|
85-
| iOS / macOS | `.mobileconfig` (Apple Configuration Profile) | PKCS#12 identity, WiFi CA, root CA, 802.1X/EAP-TLS config; optionally CMS-signed |
114+
| iOS / macOS | `.mobileconfig` (Apple Configuration Profile) | SCEP payload, WiFi CA, root CA, code-signing CA, 802.1X/EAP-TLS config; optionally CMS-signed |
86115
| Android | `.p12` (PKCS#12) | Client cert + key + WiFi CA, imported via Android WiFi settings |
87116
| Windows | `.xml` (WLAN profile) + `.p12` (PKCS#12) | EAP-TLS config and CA thumbprint; cert imported separately into the Windows certificate store |
88117

89-
The iOS mobileconfig always embeds the WiFi intermediate CA and root CA so the full trust chain is installed in one step. When `PINT_IPA_CODE_SIGNING_CA_NAME` is set, PINT also embeds the code-signing intermediate CA and wraps the profile in a CMS `SignedData` envelope, letting iOS display it as "Verified" after the CA profile is trusted.
118+
The iOS mobileconfig always embeds the WiFi intermediate CA and root CA so the full trust chain is installed in one step. The SCEP payload instructs iOS to generate a keypair on-device, enroll with PINT's `/scep` endpoint using a one-time challenge, and renew automatically near expiry — users never need to re-download the profile. When `PINT_IPA_CODE_SIGNING_CA_NAME` is set, PINT also embeds the code-signing intermediate CA and wraps the profile in a CMS `SignedData` envelope, letting iOS display it as "Verified" after the CA profile is trusted.
90119

91120
### WiFi Controller Enrollment
92121

@@ -315,6 +344,7 @@ Set `envSecret` in your values to the name of this Secret. The chart mounts it v
315344
| `helm` | Deploy the chart into kind |
316345
| `kubectl` | Interact with the dev cluster |
317346
| `overmind` | Run the Procfile (PINT + FreeIPA stub simultaneously) |
347+
| `caddy` | HTTPS reverse proxy for local SCEP testing (iOS requires TLS) |
318348

319349
### Local Setup
320350

@@ -332,10 +362,11 @@ cp .env.dev.example .env.dev
332362
make dev
333363
```
334364

335-
`make dev` starts two processes via `overmind` and the `Procfile`:
365+
`make dev` starts three processes via `overmind` and the `Procfile`:
336366

337367
- **`ipa-stub`**: FreeIPA stub server on `:8088` (see [FreeIPA Stub](#freeipa-stub) below).
338368
- **`pint`**: the PINT server on `:8080`. It waits for the stub to be ready before starting.
369+
- **`caddy`**: HTTPS reverse proxy from `https://localhost:8443` to `http://localhost:8080`. Only starts when `PINT_SERVER_URL=https://localhost:8443` (see [Testing SCEP Locally](#testing-scep-locally) below); otherwise it idles.
339370

340371
FreeRADIUS runs in the kind cluster and persists between `make dev` sessions. PINT talks to it via the Kubernetes API using your local `~/.kube/config`.
341372

@@ -392,10 +423,31 @@ All configuration is via environment variables. Copy `.env.dev.example` to `.env
392423
| `PINT_RADIUS_STATUS_PORT` | `18121` | FreeRADIUS status server port |
393424
| `PINT_RADIUS_RADSEC_CHECK_CRL` | `true` | Enable CRL checking in the RadSec TLS listener |
394425
| `PINT_RADIUS_RADSEC_PROXY_PROTOCOL` | `false` | Expect HAProxy PROXY protocol header on RadSec connections; set `true` when HAProxy fronts FreeRADIUS |
426+
| `PINT_SCEP_RA_CERT_SECRET` | `pint-scep-ra-cert` | K8s Secret for the SCEP Registration Authority (RA) certificate and key; auto-generated on first startup |
395427
| `PINT_IPA_SKIP_TLS_VERIFY` | `false` | Skip FreeIPA TLS verification (dev only) |
396428
| `PINT_DISABLE_OIDC` | `false` | Bypass OIDC and inject a static dev user |
397429
| `PINT_DEV_RTP` | `false` | Inject `rtp` group into dev user (requires `PINT_DISABLE_OIDC=true`) |
398430
431+
### Testing SCEP Locally
432+
433+
iOS requires HTTPS to complete SCEP enrollment, so local testing needs a TLS frontend. Caddy handles this automatically when you set `PINT_SERVER_URL` to the local HTTPS address in `.env.dev`:
434+
435+
```bash
436+
# .env.dev
437+
PINT_SERVER_URL=https://localhost:8443
438+
PINT_DISABLE_OIDC=true # skip OIDC so you can download profiles without a Keycloak session
439+
```
440+
441+
With those two variables set, `make dev` will start Caddy alongside PINT. On first run, Caddy generates a local CA and certificate. Trust it system-wide so the iOS profile installer accepts the mobileconfig:
442+
443+
```bash
444+
sudo security add-trusted-cert -d -r trustRoot \
445+
-k /Library/Keychains/System.keychain \
446+
"$HOME/Library/Application Support/Caddy/pki/authorities/local/root.crt"
447+
```
448+
449+
After that, download a profile from `https://localhost:8443/profile`, install it on a device on the same network, and watch PINT's logs for the SCEP `PKIOperation` request and the resulting cert issuance. The SCEP Registration Authority (RA) certificate is auto-generated and stored in the `pint-scep-ra-cert` Kubernetes Secret on first startup; it persists across restarts.
450+
399451
### FreeIPA Stub
400452

401453
The stub (`dev/freeipa-stub/`) is a minimal HTTPS server that implements just enough of the FreeIPA JSON-RPC API for PINT to function locally. It runs on `:8088` with a self-signed TLS certificate, so `PINT_IPA_SKIP_TLS_VERIFY=true` must be set in `.env.dev`.
@@ -433,7 +485,8 @@ The stub maps profile IDs to EKU and validity:
433485
|---|---|---|---|
434486
| `pint_radsec_server` | `serverAuth` | 90 days | DNS SAN set to CSR CN (required for Go TLS verification) |
435487
| `pint_profile_signing` | `codeSigning` | 1 year | Only available when `PINT_IPA_CODE_SIGNING_CA_NAME` is set |
436-
| all others (`pint_wifi`, `pint_radsec_client`, …) | `clientAuth` | 5 years | |
488+
| `pint_wifi` | `clientAuth` | 1 year | Accepts RSA and EC public keys |
489+
| `pint_radsec_client` and others | `clientAuth` | 5 years | |
437490

438491
Unlike real FreeIPA/Dogtag, the stub does not enforce subject name patterns or key type constraints defined in the profile config files.
439492

chart/templates/_helpers.tpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ never collide. Each can be overridden via the corresponding values key.
5858
{{- .Values.secrets.profileSigningCert | default (printf "%s-profile-signing-cert" (include "pint.fullname" .)) }}
5959
{{- end }}
6060

61+
{{- define "pint.secretName.scepRACert" -}}
62+
{{- .Values.secrets.scepRACert | default (printf "%s-scep-ra-cert" (include "pint.fullname" .)) }}
63+
{{- end }}
64+
6165
{{- define "pint.envSecret" -}}
6266
{{- .Values.envSecret | default (include "pint.fullname" .) }}
6367
{{- end }}

chart/templates/deployment.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ spec:
9696
value: {{ include "pint.secretName.config" . | quote }}
9797
- name: PINT_RADSEC_CERT_SECRET
9898
value: {{ include "pint.secretName.radSecCert" . | quote }}
99+
- name: PINT_SCEP_RA_CERT_SECRET
100+
value: {{ include "pint.secretName.scepRACert" . | quote }}
99101
{{- if .Values.freeradius.enabled }}
100102
- name: PINT_FREERADIUS_DEPLOYMENT
101103
value: {{ include "pint.freeradiusFullname" . | quote }}

chart/templates/role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ rules:
1414
resourceNames:
1515
- {{ include "pint.secretName.config" . | quote }}
1616
- {{ include "pint.secretName.radSecCert" . | quote }}
17+
- {{ include "pint.secretName.scepRACert" . | quote }}
1718
{{- if .Values.config.codeSigningCAName }}
1819
- {{ include "pint.secretName.profileSigningCert" . | quote }}
1920
{{- end }}

chart/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ secrets:
6565
config: "" # default: <fullname>-config (clients.json, clients.conf, status-secret, status)
6666
radSecCert: "" # default: <fullname>-radsec-server-certificates (tls.crt, tls.key, ca.pem, wifi-ca.pem)
6767
profileSigningCert: "" # default: <fullname>-profile-signing-cert (tls.crt, tls.key); only created when codeSigningCAName is set
68+
scepRACert: "" # default: <fullname>-scep-ra-cert (tls.crt, tls.key); auto-generated on first startup
6869

6970
service:
7071
type: ClusterIP

cmd/pint/main.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33

44
import (
55
"context"
6+
"crypto/rsa"
67
"crypto/x509"
78
"encoding/pem"
89
"fmt"
@@ -18,6 +19,7 @@ import (
1819
"github.com/ComputerScienceHouse/pint/internal/logger"
1920
"github.com/ComputerScienceHouse/pint/internal/profile"
2021
"github.com/ComputerScienceHouse/pint/internal/radius"
22+
internscep "github.com/ComputerScienceHouse/pint/internal/scep"
2123
"github.com/gin-contrib/multitemplate"
2224
"github.com/gin-gonic/gin"
2325
"go.uber.org/zap"
@@ -164,6 +166,15 @@ func main() {
164166
// Background watcher: renew cert before expiry and reload FreeRADIUS
165167
go watchRadSecServerCert(log, k8sClient, ipaClient, cfg, []byte(radSecCAChainPEM), wifiCAPEM)
166168

169+
// Load or generate the SCEP RA cert (self-signed RSA, stored in K8s secret).
170+
scepRACert, scepRAKey, scepRACertDER, err := loadOrGenerateSCEPRACert(context.Background(), log, k8sClient, cfg)
171+
if err != nil {
172+
log.Fatal("scep RA cert init failed", zap.Error(err))
173+
}
174+
log.Info("scep RA cert loaded", zap.String("subject", scepRACert.Subject.CommonName), zap.String("expires", scepRACert.NotAfter.Format("2006-01-02")))
175+
176+
challengeStore := internscep.NewChallengeStore()
177+
167178
// Load or request the profile signing cert when code signing is configured.
168179
var appleSigner *profile.Signer
169180
if cfg.CodeSigningCAName != "" {
@@ -214,6 +225,13 @@ func main() {
214225
r.GET("/auth/logout", auth.HandleLogout)
215226
}
216227

228+
// SCEP public routes — iOS calls these without a session cookie.
229+
scepHandler, err := internscep.NewHandler(log, challengeStore, ipaClient, cfg.IPAWirelessCAName, cfg.IPACertProfile, scepRACert, scepRAKey, caDER, rootCACertDER)
230+
if err != nil {
231+
log.Fatal("scep handler init failed", zap.Error(err))
232+
}
233+
scepHandler.Register(r)
234+
217235
// Public routes
218236
r.GET("/", handlers.IndexHandler(cfg.LoginURL))
219237

@@ -225,7 +243,7 @@ func main() {
225243
{
226244
protected.GET("/dashboard", handlers.DashboardHandler())
227245
protected.GET("/profile", handlers.ProfilePageHandler(cfg))
228-
protected.POST("/profile/generate", handlers.GenerateProfileHandler(log, ipaClient, cfg, caDER, rootCACertDER, codeSigningCACertDER, appleSigner))
246+
protected.POST("/profile/generate", handlers.GenerateProfileHandler(log, ipaClient, cfg, caDER, rootCACertDER, codeSigningCACertDER, scepRACertDER, challengeStore, appleSigner))
229247
protected.GET("/profile/ca", handlers.CAHandler(caDER))
230248
protected.GET("/radius", handlers.RadiusPageHandler(cfg, k8sClient, radSecCAChainPEM))
231249
protected.POST("/radius/secret", handlers.SaveSecretHandler(log, ipaClient, cfg, k8sClient, radSecCAChainPEM))
@@ -386,7 +404,7 @@ func loadOrRenewProfileSigningCert(ctx context.Context, log *zap.Logger, k8sClie
386404
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
387405
ecKeyBytes, err := x509.MarshalECPrivateKey(privKey)
388406
if err != nil {
389-
return nil, false, fmt.Errorf("marshal profile signing key: %w", err)
407+
return nil, false, fmt.Errorf("marshal profile signing ec key: %w", err)
390408
}
391409
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: ecKeyBytes})
392410

@@ -428,6 +446,40 @@ func profileSignerFromPEM(cert *x509.Certificate, keyPEM, codeSigningCACertDER [
428446
return s, nil
429447
}
430448

449+
// loadOrGenerateSCEPRACert loads the self-signed SCEP RA cert from the K8s Secret,
450+
// generating a new one if absent. Returns the parsed cert, key, and raw DER bytes.
451+
func loadOrGenerateSCEPRACert(ctx context.Context, log *zap.Logger, k8sClient kubernetes.Interface, cfg *config.Config) (*x509.Certificate, *rsa.PrivateKey, []byte, error) {
452+
secret, err := k8sClient.CoreV1().Secrets(cfg.Namespace).Get(ctx, cfg.SCEPRACertSecret, metav1.GetOptions{})
453+
if err == nil {
454+
certPEM := secret.Data["tls.crt"]
455+
keyPEM := secret.Data["tls.key"]
456+
if len(certPEM) > 0 && len(keyPEM) > 0 {
457+
cert, key, parseErr := internscep.ParseRACert(certPEM, keyPEM)
458+
if parseErr == nil {
459+
log.Info("reusing existing SCEP RA cert", zap.String("expires", cert.NotAfter.Format("2006-01-02")))
460+
return cert, key, cert.Raw, nil
461+
}
462+
}
463+
}
464+
465+
cert, key, certPEM, keyPEM, genErr := internscep.GenerateRACert()
466+
if genErr != nil {
467+
return nil, nil, nil, fmt.Errorf("generate SCEP RA cert: %w", genErr)
468+
}
469+
470+
raSecret := &corev1.Secret{
471+
ObjectMeta: metav1.ObjectMeta{Name: cfg.SCEPRACertSecret, Namespace: cfg.Namespace},
472+
Data: map[string][]byte{"tls.crt": certPEM, "tls.key": keyPEM},
473+
}
474+
if _, createErr := k8sClient.CoreV1().Secrets(cfg.Namespace).Create(ctx, raSecret, metav1.CreateOptions{}); createErr != nil {
475+
if _, updateErr := k8sClient.CoreV1().Secrets(cfg.Namespace).Update(ctx, raSecret, metav1.UpdateOptions{}); updateErr != nil {
476+
return nil, nil, nil, fmt.Errorf("store SCEP RA cert: %w", updateErr)
477+
}
478+
}
479+
log.Info("generated new SCEP RA cert")
480+
return cert, key, cert.Raw, nil
481+
}
482+
431483
// watchProfileSigningCert runs forever, checking daily whether the profile signing cert
432484
// needs renewal. Renewed certs are stored in K8s; pint picks them up on next restart.
433485
func watchProfileSigningCert(log *zap.Logger, k8sClient kubernetes.Interface, ipaClient *freeipa.Client, cfg *config.Config, codeSigningCACertDER []byte) {

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ require (
77
github.com/gin-contrib/multitemplate v1.1.2
88
github.com/gin-gonic/gin v1.12.0
99
github.com/google/uuid v1.6.0
10+
github.com/smallstep/scep v0.0.0-20260331191114-261f960a40d1
11+
go.uber.org/zap v1.28.0
1012
howett.net/plist v1.0.1
1113
k8s.io/api v0.36.0
1214
k8s.io/apimachinery v0.36.0
1315
k8s.io/client-go v0.36.0
14-
go.uber.org/zap v1.28.0
1516
software.sslmate.com/src/go-pkcs12 v0.7.1
1617
)
1718

@@ -53,6 +54,7 @@ require (
5354
github.com/quic-go/qpack v0.6.0 // indirect
5455
github.com/quic-go/quic-go v0.59.0 // indirect
5556
github.com/sirupsen/logrus v1.9.4 // indirect
57+
github.com/smallstep/pkcs7 v0.2.1 // indirect
5658
github.com/spf13/pflag v1.0.9 // indirect
5759
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
5860
github.com/ugorji/go/codec v1.3.1 // indirect

0 commit comments

Comments
 (0)