@@ -15,8 +15,10 @@ package config
1515
1616import (
1717 "context"
18+ "crypto/md5"
1819 "crypto/tls"
1920 "crypto/x509"
21+ "encoding/hex"
2022 "encoding/json"
2123 "errors"
2224 "fmt"
@@ -77,7 +79,7 @@ var invalidHTTPClientConfigs = []struct {
7779 },
7880 {
7981 httpClientConfigFile : "testdata/http.conf.empty.bad.yml" ,
80- errMsg : "at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured" ,
82+ errMsg : "at most one of basic_auth, digest_auth, oauth2, bearer_token & bearer_token_file must be configured" ,
8183 },
8284 {
8385 httpClientConfigFile : "testdata/http.conf.basic-auth.too-much.bad.yaml" ,
@@ -97,11 +99,15 @@ var invalidHTTPClientConfigs = []struct {
9799 },
98100 {
99101 httpClientConfigFile : "testdata/http.conf.basic-auth-and-auth-creds.too-much.bad.yaml" ,
100- errMsg : "at most one of basic_auth, oauth2 & authorization must be configured" ,
102+ errMsg : "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured" ,
101103 },
102104 {
103105 httpClientConfigFile : "testdata/http.conf.basic-auth-and-oauth2.too-much.bad.yaml" ,
104- errMsg : "at most one of basic_auth, oauth2 & authorization must be configured" ,
106+ errMsg : "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured" ,
107+ },
108+ {
109+ httpClientConfigFile : "testdata/http.conf.basic-auth-and-digest.too-much.bad.yaml" ,
110+ errMsg : "at most one of basic_auth, digest_auth, oauth2 & authorization must be configured" ,
105111 },
106112 {
107113 httpClientConfigFile : "testdata/http.conf.auth-creds-no-basic.bad.yaml" ,
@@ -312,6 +318,31 @@ func TestNewClientFromConfig(t *testing.T) {
312318 fmt .Fprint (w , ExpectedMessage )
313319 }
314320 },
321+ }, {
322+ clientConfig : HTTPClientConfig {
323+ BasicAuth : & BasicAuth {
324+ Username : ExpectedUsername ,
325+ Password : ExpectedPassword ,
326+ },
327+ TLSConfig : TLSConfig {
328+ CAFile : TLSCAChainPath ,
329+ CertFile : ClientCertificatePath ,
330+ KeyFile : ClientKeyNoPassPath ,
331+ ServerName : "" ,
332+ InsecureSkipVerify : false },
333+ },
334+ handler : func (w http.ResponseWriter , r * http.Request ) {
335+ username , password , ok := r .BasicAuth ()
336+ if ! ok {
337+ fmt .Fprintf (w , "The Authorization header wasn't set" )
338+ } else if ExpectedUsername != username {
339+ fmt .Fprintf (w , "The expected username (%s) differs from the obtained username (%s)." , ExpectedUsername , username )
340+ } else if ExpectedPassword != password {
341+ fmt .Fprintf (w , "The expected password (%s) differs from the obtained password (%s)." , ExpectedPassword , password )
342+ } else {
343+ fmt .Fprint (w , ExpectedMessage )
344+ }
345+ },
315346 }, {
316347 clientConfig : HTTPClientConfig {
317348 Authorization : & Authorization {
@@ -335,7 +366,7 @@ func TestNewClientFromConfig(t *testing.T) {
335366 },
336367 }, {
337368 clientConfig : HTTPClientConfig {
338- BasicAuth : & BasicAuth {
369+ DigestAuth : & DigestAuth {
339370 Username : ExpectedUsername ,
340371 Password : ExpectedPassword ,
341372 },
@@ -347,14 +378,61 @@ func TestNewClientFromConfig(t *testing.T) {
347378 InsecureSkipVerify : false },
348379 },
349380 handler : func (w http.ResponseWriter , r * http.Request ) {
350- username , password , ok := r .BasicAuth ()
351- if ! ok {
352- fmt .Fprintf (w , "The Authorization header wasn't set" )
353- } else if ExpectedUsername != username {
354- fmt .Fprintf (w , "The expected username (%s) differs from the obtained username (%s)." , ExpectedUsername , username )
355- } else if ExpectedPassword != password {
356- fmt .Fprintf (w , "The expected password (%s) differs from the obtained password (%s)." , ExpectedPassword , password )
381+ // Example server response header:
382+ // WWW-Authenticate: Digest realm="prometheus", nonce="43568ca162f46c3bcc57ecae193b3159", qop="auth", opaque="3bc9f19d8195721e24469ff255750f8c", algorithm=MD5, stale=FALSE
383+ //
384+ // Example client request header:
385+ // Authorization: Digest username="foo", realm="prometheus", nonce="43568ca162f46c3bcc57ecae193b3159", uri="/", cnonce="NDA2M2JmYzQ2YTQ4OTQ0OTQ1NzE0NmI3ZmYyY2YyNzU=", nc=00000001, qop=auth, response="fe543d7eeb2d2f0aba8d100a1f076909", opaque="3bc9f19d8195721e24469ff255750f8c", algorithm=MD5
386+
387+ const (
388+ nonce = "43568ca162f46c3bcc57ecae193b3159"
389+ realm = "prometheus"
390+ )
391+
392+ if authHeader := r .Header .Get ("Authorization" ); authHeader == "" {
393+ // first request
394+ w .Header ().Set ("www-authenticate" , "Digest realm=\" " + realm + "\" , nonce=\" " + nonce + "\" , qop=\" auth\" , opaque=\" 3bc9f19d8195721e24469ff255750f8c\" , algorithm=MD5, stale=FALSE" )
395+ w .WriteHeader (401 )
357396 } else {
397+ // second, authenticated request
398+ if ! strings .HasPrefix (authHeader , "Digest" ) {
399+ fmt .Fprint (w , "Request does not contain a valid digest auth header" )
400+ return
401+ }
402+
403+ digestComponents := make (map [string ]string )
404+ for _ , p := range strings .Split (authHeader , ", " )[1 :] {
405+ kvParts := strings .Split (p , "=" )
406+ digestComponents [kvParts [0 ]] = strings .TrimSpace (strings .Trim (kvParts [1 ], "\" " ))
407+ }
408+
409+ if v := digestComponents ["realm" ]; v != realm {
410+ fmt .Fprintf (w , "Digest auth with wrong realm (%s)" , v )
411+ return
412+ }
413+ if v := digestComponents ["nonce" ]; v != nonce {
414+ fmt .Fprintf (w , "Digest auth with wrong nonce (%s)" , v )
415+ return
416+ }
417+
418+ hashMD5 := func (s string ) string {
419+ hasher := md5 .New ()
420+ hasher .Write ([]byte (s ))
421+ return hex .EncodeToString (hasher .Sum (nil ))
422+ }
423+
424+ hash1Str := fmt .Sprintf ("%s:%s:%s" , ExpectedUsername , realm , ExpectedPassword )
425+ hash1 := hashMD5 (hash1Str )
426+ hash2Str := fmt .Sprintf ("GET:%s" , digestComponents ["uri" ])
427+ hash2 := hashMD5 (hash2Str )
428+ responseStr := fmt .Sprintf ("%s:%s:%s:%s:%s:%s" , hash1 , nonce , digestComponents ["nc" ], digestComponents ["cnonce" ], digestComponents ["qop" ], hash2 )
429+ response := hashMD5 (responseStr )
430+
431+ if response != digestComponents ["response" ] {
432+ fmt .Fprintf (w , "Digest auth failed, response hashes didn't match" )
433+ return
434+ }
435+
358436 fmt .Fprint (w , ExpectedMessage )
359437 }
360438 },
0 commit comments