Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | package httpcaddyfile |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "testing" |
| 6 | "time" |
| 7 | |
| 8 | "github.com/caddyserver/caddy/v2" |
| 9 | "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" |
| 10 | "github.com/caddyserver/caddy/v2/modules/caddytls" |
| 11 | _ "github.com/caddyserver/caddy/v2/modules/logging" |
| 12 | ) |
| 13 | |
| 14 | func TestGlobalLogOptionSyntax(t *testing.T) { |
| 15 | for i, tc := range []struct { |
| 16 | input string |
| 17 | output string |
| 18 | expectError bool |
| 19 | }{ |
| 20 | // NOTE: Additional test cases of successful Caddyfile parsing |
| 21 | // are present in: caddytest/integration/caddyfile_adapt/ |
| 22 | { |
| 23 | input: `{ |
| 24 | log default |
| 25 | } |
| 26 | `, |
| 27 | output: `{}`, |
| 28 | expectError: false, |
| 29 | }, |
| 30 | { |
| 31 | input: `{ |
| 32 | log example { |
| 33 | output file foo.log |
| 34 | } |
| 35 | log example { |
| 36 | format json |
| 37 | } |
| 38 | } |
| 39 | `, |
| 40 | expectError: true, |
| 41 | }, |
| 42 | { |
| 43 | input: `{ |
| 44 | log example /foo { |
| 45 | output file foo.log |
| 46 | } |
| 47 | } |
| 48 | `, |
| 49 | expectError: true, |
| 50 | }, |
| 51 | } { |
| 52 | |
| 53 | adapter := caddyfile.Adapter{ |
| 54 | ServerType: ServerType{}, |
| 55 | } |
| 56 | |
| 57 | out, _, err := adapter.Adapt([]byte(tc.input), nil) |
| 58 | |
| 59 | if err != nil != tc.expectError { |
| 60 | t.Errorf("Test %d error expectation failed Expected: %v, got %v", i, tc.expectError, err) |
| 61 | continue |
| 62 | } |
| 63 | |
| 64 | if string(out) != tc.output { |
| 65 | t.Errorf("Test %d error output mismatch Expected: %s, got %s", i, tc.output, out) |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | func TestGlobalResolversOption(t *testing.T) { |
| 71 | tests := []struct { |
| 72 | name string |
| 73 | input string |
| 74 | expectResolvers []string |
| 75 | expectError bool |
| 76 | }{ |
| 77 | { |
| 78 | name: "single resolver", |
| 79 | input: `{ |
| 80 | tls_resolvers 1.1.1.1 |
| 81 | } |
| 82 | example.com { |
| 83 | }`, |
| 84 | expectResolvers: []string{"1.1.1.1"}, |
| 85 | expectError: false, |
| 86 | }, |
| 87 | { |
| 88 | name: "two resolvers", |
| 89 | input: `{ |
| 90 | tls_resolvers 1.1.1.1 8.8.8.8 |
| 91 | } |
| 92 | example.com { |
| 93 | }`, |
| 94 | expectResolvers: []string{"1.1.1.1", "8.8.8.8"}, |
| 95 | expectError: false, |
| 96 | }, |
| 97 | { |
| 98 | name: "multiple resolvers", |
| 99 | input: `{ |
| 100 | tls_resolvers 1.1.1.1 8.8.8.8 9.9.9.9 |
| 101 | } |
| 102 | example.com { |
| 103 | }`, |
| 104 | expectResolvers: []string{"1.1.1.1", "8.8.8.8", "9.9.9.9"}, |
| 105 | expectError: false, |
| 106 | }, |
| 107 | { |
| 108 | name: "no resolvers specified", |
| 109 | input: `{ |
| 110 | } |
| 111 | example.com { |
| 112 | }`, |
| 113 | expectResolvers: nil, |
| 114 | expectError: false, |
| 115 | }, |
| 116 | } |
| 117 | |
| 118 | for _, tc := range tests { |
| 119 | t.Run(tc.name, func(t *testing.T) { |
| 120 | adapter := caddyfile.Adapter{ |
| 121 | ServerType: ServerType{}, |
| 122 | } |
| 123 | |
| 124 | out, _, err := adapter.Adapt([]byte(tc.input), nil) |
| 125 | |
| 126 | if (err != nil) != tc.expectError { |
| 127 | t.Errorf("error expectation failed. Expected error: %v, got: %v", tc.expectError, err) |
| 128 | return |
| 129 | } |
| 130 | |
| 131 | if tc.expectError { |
| 132 | return |
| 133 | } |
| 134 | |
| 135 | // Parse the output JSON to check resolvers |
| 136 | var config struct { |
| 137 | Apps struct { |
| 138 | TLS *caddytls.TLS `json:"tls"` |
| 139 | } `json:"apps"` |
| 140 | } |
| 141 | |
| 142 | if err := json.Unmarshal(out, &config); err != nil { |
| 143 | t.Errorf("failed to unmarshal output: %v", err) |
| 144 | return |
| 145 | } |
| 146 | |
| 147 | // Check if resolvers match expected |
| 148 | if config.Apps.TLS == nil { |
| 149 | if tc.expectResolvers != nil { |
| 150 | t.Errorf("Expected TLS config with resolvers %v, but TLS config is nil", tc.expectResolvers) |
| 151 | } |
| 152 | return |
| 153 | } |
| 154 | |
| 155 | actualResolvers := config.Apps.TLS.Resolvers |
| 156 | if len(tc.expectResolvers) == 0 && len(actualResolvers) == 0 { |
| 157 | return // Both empty, ok |
| 158 | } |
| 159 | if len(actualResolvers) != len(tc.expectResolvers) { |
| 160 | t.Errorf("Expected %d resolvers, got %d. Expected: %v, got: %v", len(tc.expectResolvers), len(actualResolvers), tc.expectResolvers, actualResolvers) |
| 161 | return |
| 162 | } |
| 163 | for j, expected := range tc.expectResolvers { |
| 164 | if actualResolvers[j] != expected { |
| 165 | t.Errorf("Resolver %d mismatch. Expected: %s, got: %s", j, expected, actualResolvers[j]) |
| 166 | } |
| 167 | } |
| 168 | }) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | func TestGlobalCertIssuerAppliesToImplicitACMEIssuer(t *testing.T) { |
| 173 | adapter := caddyfile.Adapter{ |
| 174 | ServerType: ServerType{}, |
| 175 | } |
| 176 | |
| 177 | input := `{ |
| 178 | cert_issuer acme { |
| 179 | disable_tlsalpn_challenge |
| 180 | } |
| 181 | } |
| 182 | report.company.intern { |
| 183 | tls { |
| 184 | ca https://deglacme01.company.intern/acme/acme/directory |
| 185 | ca_root /etc/certs/company_root2.crt |
| 186 | } |
| 187 | respond "ok" |
| 188 | }` |
| 189 | |
| 190 | out, _, err := adapter.Adapt([]byte(input), nil) |
| 191 | if err != nil { |
| 192 | t.Fatalf("adapting caddyfile: %v", err) |
| 193 | } |
| 194 | |
| 195 | var config struct { |
| 196 | Apps struct { |
| 197 | TLS *caddytls.TLS `json:"tls"` |
| 198 | } `json:"apps"` |
| 199 | } |
| 200 | if err := json.Unmarshal(out, &config); err != nil { |
| 201 | t.Fatalf("unmarshaling adapted config: %v", err) |
| 202 | } |
| 203 | if config.Apps.TLS == nil || config.Apps.TLS.Automation == nil { |
| 204 | t.Fatal("expected tls automation config") |
| 205 | } |
| 206 | |
| 207 | var subjectPolicy *caddytls.AutomationPolicy |
| 208 | for _, ap := range config.Apps.TLS.Automation.Policies { |
| 209 | if len(ap.SubjectsRaw) == 1 && ap.SubjectsRaw[0] == "report.company.intern" { |
| 210 | subjectPolicy = ap |
| 211 | break |
| 212 | } |
| 213 | } |
| 214 | if subjectPolicy == nil { |
| 215 | t.Fatal("expected subject-specific automation policy") |
| 216 | } |
| 217 | if len(subjectPolicy.IssuersRaw) != 1 { |
| 218 | t.Fatalf("expected one issuer for subject-specific policy, got %d", len(subjectPolicy.IssuersRaw)) |
| 219 | } |
| 220 | |
| 221 | var issuer caddytls.ACMEIssuer |
| 222 | if err := json.Unmarshal(subjectPolicy.IssuersRaw[0], &issuer); err != nil { |
| 223 | t.Fatalf("unmarshaling issuer: %v", err) |
| 224 | } |
| 225 | if issuer.CA != "https://deglacme01.company.intern/acme/acme/directory" { |
| 226 | t.Fatalf("expected custom ACME CA, got %q", issuer.CA) |
| 227 | } |
| 228 | if len(issuer.TrustedRootsPEMFiles) != 1 || issuer.TrustedRootsPEMFiles[0] != "/etc/certs/company_root2.crt" { |
| 229 | t.Fatalf("expected trusted roots to include site CA root, got %v", issuer.TrustedRootsPEMFiles) |
| 230 | } |
| 231 | if issuer.Challenges == nil || issuer.Challenges.TLSALPN == nil || !issuer.Challenges.TLSALPN.Disabled { |
| 232 | t.Fatalf("expected tls-alpn challenge to be disabled, got %#v", issuer.Challenges) |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | func TestMergeACMEIssuers(t *testing.T) { |
| 237 | base := &caddytls.ACMEIssuer{ |
| 238 | Email: "ops@example.com", |
| 239 | Challenges: &caddytls.ChallengesConfig{ |
| 240 | HTTP: &caddytls.HTTPChallengeConfig{ |
| 241 | AlternatePort: 8080, |
| 242 | }, |
| 243 | TLSALPN: &caddytls.TLSALPNChallengeConfig{ |
| 244 | Disabled: true, |
| 245 | AlternatePort: 8443, |
| 246 | }, |
| 247 | DNS: &caddytls.DNSChallengeConfig{ |
| 248 | Resolvers: []string{"1.1.1.1"}, |
| 249 | OverrideDomain: "_acme-challenge.example.net", |
| 250 | }, |
| 251 | }, |
| 252 | TrustedRootsPEMFiles: []string{"global.pem"}, |
| 253 | } |
| 254 | overrides := &caddytls.ACMEIssuer{ |
| 255 | CA: "https://deglacme01.company.intern/acme/acme/directory", |
| 256 | Challenges: &caddytls.ChallengesConfig{ |
| 257 | HTTP: &caddytls.HTTPChallengeConfig{ |
| 258 | Disabled: true, |
| 259 | }, |
| 260 | DNS: &caddytls.DNSChallengeConfig{ |
| 261 | PropagationTimeout: caddy.Duration(time.Minute), |
| 262 | }, |
| 263 | }, |
| 264 | TrustedRootsPEMFiles: []string{"site.pem"}, |
| 265 | } |
| 266 | |
| 267 | merged := mergeACMEIssuers(base, overrides) |
| 268 | if merged.CA != overrides.CA { |
| 269 | t.Fatalf("expected merged CA %q, got %q", overrides.CA, merged.CA) |
| 270 | } |
| 271 | if merged.Email != base.Email { |
| 272 | t.Fatalf("expected merged email %q, got %q", base.Email, merged.Email) |
| 273 | } |
| 274 | if len(merged.TrustedRootsPEMFiles) != 2 || merged.TrustedRootsPEMFiles[0] != "global.pem" || merged.TrustedRootsPEMFiles[1] != "site.pem" { |
| 275 | t.Fatalf("expected merged roots [global.pem site.pem], got %v", merged.TrustedRootsPEMFiles) |
| 276 | } |
| 277 | if merged.Challenges == nil || merged.Challenges.HTTP == nil || !merged.Challenges.HTTP.Disabled || merged.Challenges.HTTP.AlternatePort != 8080 { |
| 278 | t.Fatalf("expected merged HTTP challenge config to preserve alternate port and apply disable flag, got %#v", merged.Challenges) |
| 279 | } |
| 280 | if merged.Challenges.TLSALPN == nil || !merged.Challenges.TLSALPN.Disabled || merged.Challenges.TLSALPN.AlternatePort != 8443 { |
| 281 | t.Fatalf("expected merged TLS-ALPN challenge config to preserve global settings, got %#v", merged.Challenges) |
| 282 | } |
| 283 | if merged.Challenges.DNS == nil || merged.Challenges.DNS.PropagationTimeout != caddy.Duration(time.Minute) || len(merged.Challenges.DNS.Resolvers) != 1 || merged.Challenges.DNS.Resolvers[0] != "1.1.1.1" || merged.Challenges.DNS.OverrideDomain != "_acme-challenge.example.net" { |
| 284 | t.Fatalf("expected merged DNS challenge config to preserve global values and apply overrides, got %#v", merged.Challenges) |
| 285 | } |
| 286 | |
| 287 | if base.CA != "" { |
| 288 | t.Fatalf("expected base issuer to remain unchanged, got CA %q", base.CA) |
| 289 | } |
| 290 | if len(base.TrustedRootsPEMFiles) != 1 || base.TrustedRootsPEMFiles[0] != "global.pem" { |
| 291 | t.Fatalf("expected base roots to remain unchanged, got %v", base.TrustedRootsPEMFiles) |
| 292 | } |
| 293 | } |