|
4 | 4 | "context" |
5 | 5 | "fmt" |
6 | 6 | "net/http" |
| 7 | + "os" |
7 | 8 | "strings" |
8 | 9 | "time" |
9 | 10 |
|
@@ -49,13 +50,16 @@ const ( |
49 | 50 | UpstreamType = "upstream_type" |
50 | 51 | UpstreamUrl = "upstream_url" |
51 | 52 | VerifySsl = "verify_ssl" |
| 53 | + AuthCertificateKey = "auth_certificate_key" |
| 54 | + AuthCertificate = "auth_certificate" |
52 | 55 | ) |
53 | 56 |
|
54 | 57 | var ( |
55 | 58 | authModes = []string{ |
56 | 59 | "None", |
57 | 60 | "Username and Password", |
58 | 61 | "Token", |
| 62 | + "Certificate and Key", |
59 | 63 | } |
60 | 64 | upstreamModes = []string{ |
61 | 65 | "Proxy Only", |
@@ -113,6 +117,43 @@ func importUpstream(_ context.Context, d *schema.ResourceData, _ interface{}) ([ |
113 | 117 | return []*schema.ResourceData{d}, nil |
114 | 118 | } |
115 | 119 |
|
| 120 | +// readCertificateFiles reads the certificate and key files for mTLS authentication |
| 121 | +// Returns pointers to the certificate and key contents, and any error encountered |
| 122 | +func readCertificateFiles(d *schema.ResourceData) (cert, key *string, err error) { |
| 123 | + if certPath := optionalString(d, AuthCertificate); certPath != nil { |
| 124 | + certContent, err := os.ReadFile(*certPath) |
| 125 | + if err != nil { |
| 126 | + return nil, nil, fmt.Errorf("error reading auth certificate file: %w", err) |
| 127 | + } |
| 128 | + // Remove any trailing whitespace and ensure proper PEM format |
| 129 | + certStr := strings.TrimSpace(string(certContent)) |
| 130 | + if !strings.HasPrefix(certStr, "-----BEGIN CERTIFICATE-----") { |
| 131 | + return nil, nil, fmt.Errorf("invalid certificate format: must be a PEM encoded certificate") |
| 132 | + } |
| 133 | + cert = &certStr |
| 134 | + } |
| 135 | + |
| 136 | + if certKeyPath := optionalString(d, AuthCertificateKey); certKeyPath != nil { |
| 137 | + certKeyContent, err := os.ReadFile(*certKeyPath) |
| 138 | + if err != nil { |
| 139 | + return nil, nil, fmt.Errorf("error reading auth certificate key file: %w", err) |
| 140 | + } |
| 141 | + // Remove any trailing whitespace and ensure proper PEM format |
| 142 | + certKeyStr := strings.TrimSpace(string(certKeyContent)) |
| 143 | + if !strings.HasPrefix(certKeyStr, "-----BEGIN") || !strings.Contains(certKeyStr, "PRIVATE KEY-----") { |
| 144 | + return nil, nil, fmt.Errorf("invalid private key format: must be a PEM encoded private key") |
| 145 | + } |
| 146 | + key = &certKeyStr |
| 147 | + } |
| 148 | + |
| 149 | + // Both certificate and key must be provided together |
| 150 | + if (cert != nil && key == nil) || (cert == nil && key != nil) { |
| 151 | + return nil, nil, fmt.Errorf("both auth_certificate and auth_certificate_key must be provided when using Certificate and Key authentication") |
| 152 | + } |
| 153 | + |
| 154 | + return cert, key, nil |
| 155 | +} |
| 156 | + |
116 | 157 | func resourceRepositoryUpstreamCreate(d *schema.ResourceData, m interface{}) error { |
117 | 158 | pc := m.(*providerConfig) |
118 | 159 |
|
@@ -217,22 +258,34 @@ func resourceRepositoryUpstreamCreate(d *schema.ResourceData, m interface{}) err |
217 | 258 | upstream, resp, err = pc.APIClient.ReposApi.ReposUpstreamDebCreateExecute(req) |
218 | 259 | case Docker: |
219 | 260 | req := pc.APIClient.ReposApi.ReposUpstreamDockerCreate(pc.Auth, namespace, repository) |
| 261 | + |
| 262 | + // Read certificate files for mTLS authentication (Docker only for now) |
| 263 | + authCert, authCertKey, err := readCertificateFiles(d) |
| 264 | + if err != nil { |
| 265 | + return err |
| 266 | + } |
| 267 | + |
220 | 268 | req = req.Data(cloudsmith.DockerUpstreamRequest{ |
221 | | - AuthMode: authMode, |
222 | | - AuthSecret: authSecret, |
223 | | - AuthUsername: authUsername, |
224 | | - ExtraHeader1: extraHeader1, |
225 | | - ExtraHeader2: extraHeader2, |
226 | | - ExtraValue1: extraValue1, |
227 | | - ExtraValue2: extraValue2, |
228 | | - IsActive: isActive, |
229 | | - Mode: mode, |
230 | | - Name: name, |
231 | | - Priority: priority, |
232 | | - UpstreamUrl: upstreamUrl, |
233 | | - VerifySsl: verifySsl, |
| 269 | + AuthMode: authMode, |
| 270 | + AuthSecret: authSecret, |
| 271 | + AuthUsername: authUsername, |
| 272 | + AuthCertificate: authCert, |
| 273 | + AuthCertificateKey: authCertKey, |
| 274 | + ExtraHeader1: extraHeader1, |
| 275 | + ExtraHeader2: extraHeader2, |
| 276 | + ExtraValue1: extraValue1, |
| 277 | + ExtraValue2: extraValue2, |
| 278 | + IsActive: isActive, |
| 279 | + Mode: mode, |
| 280 | + Name: name, |
| 281 | + Priority: priority, |
| 282 | + UpstreamUrl: upstreamUrl, |
| 283 | + VerifySsl: verifySsl, |
234 | 284 | }) |
235 | 285 | upstream, resp, err = pc.APIClient.ReposApi.ReposUpstreamDockerCreateExecute(req) |
| 286 | + if err != nil { |
| 287 | + return err |
| 288 | + } |
236 | 289 | case Helm: |
237 | 290 | req := pc.APIClient.ReposApi.ReposUpstreamHelmCreate(pc.Auth, namespace, repository) |
238 | 291 | req = req.Data(cloudsmith.HelmUpstreamRequest{ |
@@ -622,22 +675,34 @@ func resourceRepositoryUpstreamUpdate(d *schema.ResourceData, m interface{}) err |
622 | 675 | upstream, _, err = pc.APIClient.ReposApi.ReposUpstreamDebUpdateExecute(req) |
623 | 676 | case Docker: |
624 | 677 | req := pc.APIClient.ReposApi.ReposUpstreamDockerUpdate(pc.Auth, namespace, repository, slugPerm) |
| 678 | + |
| 679 | + // Read certificate files for mTLS authentication (Docker only for now) |
| 680 | + authCert, authCertKey, err := readCertificateFiles(d) |
| 681 | + if err != nil { |
| 682 | + return err |
| 683 | + } |
| 684 | + |
625 | 685 | req = req.Data(cloudsmith.DockerUpstreamRequest{ |
626 | | - AuthMode: authMode, |
627 | | - AuthSecret: authSecret, |
628 | | - AuthUsername: authUsername, |
629 | | - ExtraHeader1: extraHeader1, |
630 | | - ExtraHeader2: extraHeader2, |
631 | | - ExtraValue1: extraValue1, |
632 | | - ExtraValue2: extraValue2, |
633 | | - IsActive: isActive, |
634 | | - Mode: mode, |
635 | | - Name: name, |
636 | | - Priority: priority, |
637 | | - UpstreamUrl: upstreamUrl, |
638 | | - VerifySsl: verifySsl, |
| 686 | + AuthMode: authMode, |
| 687 | + AuthSecret: authSecret, |
| 688 | + AuthUsername: authUsername, |
| 689 | + AuthCertificate: authCert, |
| 690 | + AuthCertificateKey: authCertKey, |
| 691 | + ExtraHeader1: extraHeader1, |
| 692 | + ExtraHeader2: extraHeader2, |
| 693 | + ExtraValue1: extraValue1, |
| 694 | + ExtraValue2: extraValue2, |
| 695 | + IsActive: isActive, |
| 696 | + Mode: mode, |
| 697 | + Name: name, |
| 698 | + Priority: priority, |
| 699 | + UpstreamUrl: upstreamUrl, |
| 700 | + VerifySsl: verifySsl, |
639 | 701 | }) |
640 | 702 | upstream, _, err = pc.APIClient.ReposApi.ReposUpstreamDockerUpdateExecute(req) |
| 703 | + if err != nil { |
| 704 | + return err |
| 705 | + } |
641 | 706 | case Helm: |
642 | 707 | req := pc.APIClient.ReposApi.ReposUpstreamHelmUpdate(pc.Auth, namespace, repository, slugPerm) |
643 | 708 | req = req.Data(cloudsmith.HelmUpstreamRequest{ |
@@ -922,6 +987,18 @@ func resourceRepositoryUpstream() *schema.Resource { |
922 | 987 | Optional: true, |
923 | 988 | ValidateFunc: validation.StringIsNotEmpty, |
924 | 989 | }, |
| 990 | + AuthCertificate: { |
| 991 | + Type: schema.TypeString, |
| 992 | + Description: "Path to the X.509 Certificate file to use for mTLS authentication against the upstream (Docker only)", |
| 993 | + Optional: true, |
| 994 | + ValidateFunc: validation.StringIsNotEmpty, |
| 995 | + }, |
| 996 | + AuthCertificateKey: { |
| 997 | + Type: schema.TypeString, |
| 998 | + Description: "Path to the Certificate key file to use for mTLS authentication against the upstream (Docker only)", |
| 999 | + Optional: true, |
| 1000 | + ValidateFunc: validation.StringIsNotEmpty, |
| 1001 | + }, |
925 | 1002 | Component: { |
926 | 1003 | Type: schema.TypeString, |
927 | 1004 | Description: "(deb only) The component to fetch from the upstream.", |
|
0 commit comments