Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
sudo apt update
sudo apt install -y nfpm gettext-base
- uses: docker/setup-qemu-action@v4
with:
cache-image: false
platforms: linux/amd64,linux/arm64
- uses: docker/setup-buildx-action@v4
with:
driver: docker-container
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ jobs:
sudo apt update
sudo apt install -y nfpm gettext-base
- uses: docker/setup-qemu-action@v4
with:
cache-image: false
platforms: linux/amd64,linux/arm64
- uses: docker/setup-buildx-action@v4
with:
driver: docker-container
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# CHANGELOG

## 2.42.0

- Added support for 2 new DNS providers for Let's Encrypt certificates: OpusDNS and Poweradmin
- Ability to skip DNS propagation checks while issuing a Let's Encrypt certificate
- Security updates and fixes
- Other minor fixes and improvements

## 2.41.1

- Fix for the scenario where multiple admin users could be created during the onboarding flow under a race condition
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ update-dependencies: .backend-prerequisites .frontend-prerequisites .update-ngin
cd integration/truenas && go get -u ./...
cd tools && go get -u ./...
cd i18n && go get -u ./...
cd vpn/netbird && go get -u ./...
cd vpn/tailscale && go get -u ./...
cd vpn/netbird && go get -u github.com/netbirdio/netbird@latest
cd vpn/tailscale && go get -u tailscale.com@latest
go work sync
cd frontend && pnpm update

Expand Down
6 changes: 3 additions & 3 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.11.1
go.uber.org/mock v0.6.0
golang.org/x/text v0.38.0
golang.org/x/text v0.39.0
)

require (
Expand All @@ -25,12 +25,12 @@ require (
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/pelletier/go-toml/v2 v2.4.0 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect
Expand Down
13 changes: 7 additions & 6 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
Expand All @@ -49,12 +49,13 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.4.0 h1:Mwu0mAkUKbittDs3/ADDWXqMmq3EOK2VHiuCkV00Row=
github.com/pelletier/go-toml/v2 v2.4.0/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
Expand Down Expand Up @@ -87,8 +88,8 @@ golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
1 change: 1 addition & 0 deletions certificate/commons/go.sum
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
55 changes: 38 additions & 17 deletions certificate/letsencrypt/acme_facade.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/pem"
"strings"
"time"

"github.com/go-acme/lego/v5/certcrypto"
acmecertificate "github.com/go-acme/lego/v5/certificate"
"github.com/go-acme/lego/v5/challenge"
"github.com/go-acme/lego/v5/challenge/dns01"
"github.com/go-acme/lego/v5/lego"
"github.com/go-acme/lego/v5/registration"
"github.com/google/uuid"
Expand Down Expand Up @@ -48,7 +48,14 @@ func issueCertificate(

client.Challenge.Remove(challenge.TLSALPN01)
client.Challenge.Remove(challenge.HTTP01)
err = client.Challenge.SetDNS01Provider(dnsChallenge)

dnsOptions := make([]dns01.ChallengeOption, 0, 1)
propagationBypassCasted, propagationBypass := parameters["bypassDnsPropagationChecks"].(bool)
if propagationBypassCasted && propagationBypass {
dnsOptions = append(dnsOptions, dns01.PropagationWait(5*time.Second, true))
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}

err = client.Challenge.SetDNS01Provider(dnsChallenge, dnsOptions...)
if err != nil {
return nil, err
}
Expand All @@ -72,6 +79,9 @@ func issueCertificate(
Bundle: true,
KeyType: certcrypto.RSA2048,
}
if productionEnvironment {
request.PreferredChain = "ISRG Root X1"
}

cert, err := client.Certificate.Obtain(ctx, request)
if err != nil {
Expand Down Expand Up @@ -100,8 +110,7 @@ func parseResult(
productionEnvironment bool,
client *lego.Client,
) (*certificate.Certificate, error) {
mainCert := strings.Replace(string(result.Certificate), string(result.IssuerCertificate), "", 1)
pemBlock, _ := pem.Decode([]byte(mainCert))
pemBlock, _ := pem.Decode(result.Certificate)
if pemBlock == nil || pemBlock.Type != "CERTIFICATE" {
return nil, coreerror.New(
i18n.M(ctx, i18n.K.CommonUnableToParsePem).V("type", "certificate"),
Expand All @@ -125,19 +134,14 @@ func parseResult(
return nil, err
}

privateKeyBlock, _ := pem.Decode(result.PrivateKey)
if privateKeyBlock == nil || privateKeyBlock.Type != "RSA PRIVATE KEY" {
privateKey, err := certcrypto.ParsePEMPrivateKey(result.PrivateKey)
if err != nil {
return nil, coreerror.New(
i18n.M(ctx, i18n.K.CommonUnableToParsePem).V("type", "private key"),
false,
)
}

privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
if err != nil {
return nil, err
}

encodedPrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return nil, err
Expand All @@ -148,7 +152,7 @@ func parseResult(
return nil, err
}

encodedCertificationChain, err := encodeIssuerCertificate(ctx, result.IssuerCertificate)
certificationChain, err := encodeIssuerCertificates(ctx, result.IssuerCertificate)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
if err != nil {
return nil, err
}
Expand All @@ -163,24 +167,41 @@ func parseResult(
RenewAfter: renewAt,
PrivateKey: base64.StdEncoding.EncodeToString(encodedPrivateKey),
PublicKey: base64.StdEncoding.EncodeToString(pemBlock.Bytes),
CertificationChain: []string{*encodedCertificationChain},
CertificationChain: certificationChain,
Parameters: parameters,
Metadata: &metadataJSON,
}

return &output, nil
}

func encodeIssuerCertificate(ctx context.Context, issuer []byte) (*string, error) {
pemBlock, _ := pem.Decode(issuer)
if pemBlock == nil || pemBlock.Type != "CERTIFICATE" {
func encodeIssuerCertificates(ctx context.Context, issuer []byte) ([]string, error) {
chain := make([]string, 0)
remaining := issuer

for {
pemBlock, rest := pem.Decode(remaining)
if pemBlock == nil {
break
}

remaining = rest

if pemBlock.Type != "CERTIFICATE" {
continue
}

chain = append(chain, base64.StdEncoding.EncodeToString(pemBlock.Bytes))
}

if len(chain) == 0 {
return nil, coreerror.New(
i18n.M(ctx, i18n.K.CommonUnableToParsePem).V("type", "issuer"),
false,
)
}

return new(base64.StdEncoding.EncodeToString(pemBlock.Bytes)), nil
return chain, nil
}

func fetchCertDates(
Expand Down
55 changes: 55 additions & 0 deletions certificate/letsencrypt/dns/opusdns/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package opusdns

import (
"context"

"github.com/go-acme/lego/v5/challenge"
"github.com/go-acme/lego/v5/providers/dns/opusdns"

"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns"
"dillmann.com.br/nginx-ignition/core/common/dynamicfields"
"dillmann.com.br/nginx-ignition/core/common/i18n"
)

//nolint:gosec
const (
apiKeyFieldID = "opusDnsApiKey"
)

type Provider struct{}

func (p *Provider) ID() string {
return "OPUS_DNS"
}

func (p *Provider) Name(ctx context.Context) *i18n.Message {
return i18n.M(ctx, i18n.K.CertificateLetsencryptDnsOpusdnsName)
}

func (p *Provider) DynamicFields(ctx context.Context) []dynamicfields.DynamicField {
return dns.LinkedToProvider(p.ID(), []dynamicfields.DynamicField{
{
ID: apiKeyFieldID,
Description: i18n.M(ctx, i18n.K.CertificateLetsencryptDnsOpusdnsApiKey),
Required: true,
Sensitive: true,
Type: dynamicfields.SingleLineTextType,
},
})
}

func (p *Provider) ChallengeProvider(
_ context.Context,
_ []string,
parameters map[string]any,
) (challenge.Provider, error) {
apiKey, _ := parameters[apiKeyFieldID].(string)

cfg := opusdns.NewDefaultConfig()
cfg.APIKey = apiKey
cfg.TTL = dns.TTL
cfg.PropagationTimeout = dns.PropagationTimeout
cfg.PollingInterval = dns.PollingInterval

return opusdns.NewDNSProviderConfig(cfg)
}
64 changes: 64 additions & 0 deletions certificate/letsencrypt/dns/poweradmin/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package poweradmin

import (
"context"

"github.com/go-acme/lego/v5/challenge"
"github.com/go-acme/lego/v5/providers/dns/poweradmin"

"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns"
"dillmann.com.br/nginx-ignition/core/common/dynamicfields"
"dillmann.com.br/nginx-ignition/core/common/i18n"
)

//nolint:gosec
const (
baseURLFieldID = "poweradminBaseUrl"
apiKeyFieldID = "poweradminApiKey"
)

type Provider struct{}

func (p *Provider) ID() string {
return "POWERADMIN"
}

func (p *Provider) Name(ctx context.Context) *i18n.Message {
return i18n.M(ctx, i18n.K.CertificateLetsencryptDnsPoweradminName)
}

func (p *Provider) DynamicFields(ctx context.Context) []dynamicfields.DynamicField {
return dns.LinkedToProvider(p.ID(), []dynamicfields.DynamicField{
{
ID: baseURLFieldID,
Description: i18n.M(ctx, i18n.K.CertificateLetsencryptDnsPoweradminBaseUrl),
Required: true,
Type: dynamicfields.URLType,
},
{
ID: apiKeyFieldID,
Description: i18n.M(ctx, i18n.K.CertificateLetsencryptDnsPoweradminApiKey),
Required: true,
Sensitive: true,
Type: dynamicfields.SingleLineTextType,
},
})
}

func (p *Provider) ChallengeProvider(
_ context.Context,
_ []string,
parameters map[string]any,
) (challenge.Provider, error) {
baseURL, _ := parameters[baseURLFieldID].(string)
apiKey, _ := parameters[apiKeyFieldID].(string)

cfg := poweradmin.NewDefaultConfig()
cfg.BaseURL = baseURL
cfg.APIKey = apiKey
cfg.TTL = dns.TTL
cfg.PropagationTimeout = dns.PropagationTimeout
cfg.PollingInterval = dns.PollingInterval

return poweradmin.NewDNSProviderConfig(cfg)
}
4 changes: 4 additions & 0 deletions certificate/letsencrypt/dns_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,15 @@ import (
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/omglol"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/onecloudru"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/onlinenet"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/opusdns"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/oraclecloud"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/otc"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/ovh"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/pdns"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/plesk"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/pointdns"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/porkbun"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/poweradmin"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/rackspace"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/rage4"
"dillmann.com.br/nginx-ignition/certificate/letsencrypt/dns/rainyun"
Expand Down Expand Up @@ -375,13 +377,15 @@ var providers = []dns.Provider{
&omglol.Provider{},
&onecloudru.Provider{},
&onlinenet.Provider{},
&opusdns.Provider{},
&oraclecloud.Provider{},
&otc.Provider{},
&ovh.Provider{},
&pdns.Provider{},
&pointdns.Provider{},
&plesk.Provider{},
&porkbun.Provider{},
&poweradmin.Provider{},
&rackspace.Provider{},
&rage4.Provider{},
&rainyun.Provider{},
Expand Down
Loading
Loading