Skip to content

feat(provider): new deployment provider: F5 Big-IP - #1349

Draft
ckyvra wants to merge 4 commits into
certimate-go:mainfrom
ckyvra:feat/f5-bigip-provider
Draft

feat(provider): new deployment provider: F5 Big-IP#1349
ckyvra wants to merge 4 commits into
certimate-go:mainfrom
ckyvra:feat/f5-bigip-provider

Conversation

@ckyvra

@ckyvra ckyvra commented Jun 20, 2026

Copy link
Copy Markdown

Closes #1350

Add support for F5 Big-IP as a deployment provider for Certimate.

Features

  • Upload certificate and private key via the iControl REST API
  • Two modes: upload only (certificate) or upload + client-ssl profile update (clientssl)
  • F5 object name auto-generated: certimate_<san> (stable across renewals)
  • Wildcard support (*. prefix stripped from the name)
  • Configurable partition (default: Common)
  • Auth via username/password with AllowInsecureConnections option

Files changed

Go (6 created, 2 modified):

  • pkg/sdk3rd/f5bigip/client.go — F5 iControl REST SDK client
  • pkg/core/deployer/providers/f5bigip/consts.go — Constants
  • pkg/core/deployer/providers/f5bigip/f5bigip.go — Implementation
  • pkg/core/deployer/providers/f5bigip/f5bigip_test.go — Tests
  • internal/certmgmt/deployers/sp_f5_bigip.go — Registry registration
  • internal/domain/provider.goAccessProviderTypeF5BigIP, DeploymentProviderTypeF5BigIP
  • internal/domain/access.goAccessConfigForF5BigIP

UI (2 created, 11 modified):

  • Credentials form, deployment form, SVG icon, EN/ZH i18n

@ckyvra
ckyvra force-pushed the feat/f5-bigip-provider branch from ec0a6ed to 7834fd0 Compare June 20, 2026 20:14
Comment on lines +134 to +149
func (c *Client) GetClientSSLProfile(ctx context.Context, name, partition string) (map[string]any, error) {
path := fmt.Sprintf("/mgmt/tm/ltm/profile/client-ssl/~%s~%s", url.PathEscape(partition), url.PathEscape(name))

httpreq, err := c.newRequest(http.MethodGet, path)
if err != nil {
return nil, err
}
httpreq.SetContext(ctx)

result := make(map[string]any)
if _, err := c.doRequestWithResult(httpreq, &result); err != nil {
return nil, fmt.Errorf("failed to get client-ssl profile: %w", err)
}

return result, nil
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this function do?

I only see the definition, but I don't find any calling.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! At the time of the PR, GetClientSSLProfile was indeed dead code — I had only defined it preemptively.

I've since addressed this in commit d099d5cc:

  1. GetClientSSLProfile is now called before UpdateClientSSLProfile to check whether the client-ssl profile exists on the F5.
  2. If the profile doesn't exist (404), it is automatically created via CreateClientSSLProfile with defaultsFrom: "clientssl", cert, key, and the certificate chain.
  3. The CA chain is now properly extracted from the PEM using ExtractCertificatesFromPEM and uploaded as a separate ssl-cert object, then referenced in the profile's chain field.

So GetClientSSLProfile now serves as the gate between "create" and "update" logic.

@fudiwei fudiwei Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed you changed the certificate importing approach from importing the fullchain directly into importing the leaf and chain separately. These are two completely different approaches. This raises a question: which one does BIG-IP actually support? 😕

Furthermore, your response appears to be a verbatim copy of a LLM output. So I have serious doubts about whether this code was ever tested in a real environment.

Don't modify the code at this stage; just focus on answering my review comments please.

English might not be your native language -- nor mine, this is understandable. But it is not justify relying entirely on AI to communicate.

Comment on lines +115 to +127
func buildCertName(cert *x509.Certificate) string {
san := ""
if len(cert.DNSNames) > 0 {
san = cert.DNSNames[0]
} else if cert.Subject.CommonName != "" {
san = cert.Subject.CommonName
} else {
return ""
}

san = strings.TrimPrefix(san, "*.")
return "certimate_" + san
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the cert name unique in BIG-IP?

If it is unique, and the first SAN is used as part of the name here, how can two certs with the same SAN be distinguished? And since you trimmed *., how can *.example.com and example.com be distinguished? Will the later uploaded ones overwrite the earlier ones?

(I don't know much about BIG-IP, so please correct me if I make any mistakes.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! The name must be unique within a partition on the BIG-IP — two ssl-cert objects with the same name cannot coexist.

I've updated the naming in commit db6a4dfc to resolve both issues you raised:

  1. *.example.com vs example.com — Instead of just trimming *., I now replace * with wildcard:

    • *.example.comcertimate_wildcard.example.com_letsencrypt_a1b2c3d4
    • example.comcertimate_example.com_letsencrypt_e5f6g7h8
  2. Two certs with the same SAN — The name now includes a short SHA1 hash (8 hex chars) of the raw certificate bytes, so renewals or different certificates for the same domain get unique names:

    • certimate_example.com_letsencrypt_a1b2c3d4 (first upload)
    • certimate_example.com_letsencrypt_e5f6g7h8 (renewal)
  3. Different CAs — The issuer is also extracted from the certificate's Organization field (e.g., letsencrypt, zerossl, googletrust), preventing collisions across CA providers.

The full pattern is now: certimate_<san>_<issuer>_<hash>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't answer my question:

Will the later uploaded ones overwrite the earlier ones?

I just found these documentations:

I noticed that it supports the PUT method, and based on my experience, this indicates that the certs and keys can be modified.

So my question remains the same: will the newly uploaded certificate overwrite the old certificate with the same name?

I don't have BIG-IP. Would you like to help me verify this?

P.S. Once again: don't modify the code at this stage.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have access to a live F5 again next week, so I can test this properly before making changes.
I'll report back with the results. Thanks for the documentation links

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, would you like to give me a JSON response data from a real GET request about quering the uploaded certificate and the key?

To avoid exposing sensitive data, you can use a self-signed certificate and key for testing. Don't forget to attach the content here. I would appreciate it.

ckyvra added 2 commits June 23, 2026 18:10
…tificate chain

- Add CreateClientSSLProfile SDK method for profile auto-creation
- Add chain certificate upload and reference in client-ssl profile
- Add ErrNotFound sentinel error for 404 detection
- Use ExtractCertificatesFromPEM to separate leaf and chain certs
…ards

Use wildcard substitution, issuer-derived prefix and short SHA1 hash to
prevent naming collisions in ssl-cert/ssl-key uploads:

  certimate_<san>_<issuer>_<hash>

- Replace * with 'wildcard' instead of stripping it
- Extract issuer slug from X.509 Organization[0] or CommonName
- Append short SHA1 hash of the raw certificate
- Sanitize issuer name against non-alphanumeric characters
@fudiwei
fudiwei marked this pull request as draft July 2, 2026 08:39
@fudiwei
fudiwei force-pushed the main branch 2 times, most recently from 773173e to f94fa07 Compare August 6, 2026 08:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: new deployment provider - F5 Big-IP

2 participants