github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/middlewares/v1/api_key.go (about) 1 package v1 2 3 import ( 4 "crypto/rand" 5 "crypto/sha512" 6 "encoding/base64" 7 "fmt" 8 "net/http" 9 "strings" 10 11 "github.com/gin-gonic/gin" 12 log "github.com/sirupsen/logrus" 13 14 "github.com/crowdsecurity/crowdsec/pkg/database" 15 "github.com/crowdsecurity/crowdsec/pkg/database/ent" 16 "github.com/crowdsecurity/crowdsec/pkg/types" 17 ) 18 19 const ( 20 APIKeyHeader = "X-Api-Key" 21 BouncerContextKey = "bouncer_info" 22 dummyAPIKeySize = 54 23 // max allowed by bcrypt 72 = 54 bytes in base64 24 ) 25 26 type APIKey struct { 27 HeaderName string 28 DbClient *database.Client 29 TlsAuth *TLSAuth 30 } 31 32 func GenerateAPIKey(n int) (string, error) { 33 bytes := make([]byte, n) 34 if _, err := rand.Read(bytes); err != nil { 35 return "", err 36 } 37 38 encoded := base64.StdEncoding.EncodeToString(bytes) 39 40 // the '=' can cause issues on some bouncers 41 return strings.TrimRight(encoded, "="), nil 42 } 43 44 func NewAPIKey(dbClient *database.Client) *APIKey { 45 return &APIKey{ 46 HeaderName: APIKeyHeader, 47 DbClient: dbClient, 48 TlsAuth: &TLSAuth{}, 49 } 50 } 51 52 func HashSHA512(str string) string { 53 hashedKey := sha512.New() 54 hashedKey.Write([]byte(str)) 55 56 hashStr := fmt.Sprintf("%x", hashedKey.Sum(nil)) 57 58 return hashStr 59 } 60 61 func (a *APIKey) authTLS(c *gin.Context, logger *log.Entry) *ent.Bouncer { 62 if a.TlsAuth == nil { 63 logger.Error("TLS Auth is not configured but client presented a certificate") 64 return nil 65 } 66 67 validCert, extractedCN, err := a.TlsAuth.ValidateCert(c) 68 if !validCert { 69 logger.Error(err) 70 return nil 71 } 72 73 if err != nil { 74 logger.Error(err) 75 return nil 76 } 77 78 logger = logger.WithFields(log.Fields{ 79 "cn": extractedCN, 80 }) 81 82 bouncerName := fmt.Sprintf("%s@%s", extractedCN, c.ClientIP()) 83 bouncer, err := a.DbClient.SelectBouncerByName(bouncerName) 84 85 // This is likely not the proper way, but isNotFound does not seem to work 86 if err != nil && strings.Contains(err.Error(), "bouncer not found") { 87 // Because we have a valid cert, automatically create the bouncer in the database if it does not exist 88 // Set a random API key, but it will never be used 89 apiKey, err := GenerateAPIKey(dummyAPIKeySize) 90 if err != nil { 91 logger.Errorf("error generating mock api key: %s", err) 92 return nil 93 } 94 95 logger.Infof("Creating bouncer %s", bouncerName) 96 97 bouncer, err = a.DbClient.CreateBouncer(bouncerName, c.ClientIP(), HashSHA512(apiKey), types.TlsAuthType) 98 if err != nil { 99 logger.Errorf("while creating bouncer db entry: %s", err) 100 return nil 101 } 102 } else if err != nil { 103 // error while selecting bouncer 104 logger.Errorf("while selecting bouncers: %s", err) 105 return nil 106 } else if bouncer.AuthType != types.TlsAuthType { 107 // bouncer was found in DB 108 logger.Errorf("bouncer isn't allowed to auth by TLS") 109 return nil 110 } 111 112 return bouncer 113 } 114 115 func (a *APIKey) authPlain(c *gin.Context, logger *log.Entry) *ent.Bouncer { 116 val, ok := c.Request.Header[APIKeyHeader] 117 if !ok { 118 logger.Errorf("API key not found") 119 return nil 120 } 121 122 hashStr := HashSHA512(val[0]) 123 124 bouncer, err := a.DbClient.SelectBouncer(hashStr) 125 if err != nil { 126 logger.Errorf("while fetching bouncer info: %s", err) 127 return nil 128 } 129 130 if bouncer.AuthType != types.ApiKeyAuthType { 131 logger.Errorf("bouncer %s attempted to login using an API key but it is configured to auth with %s", bouncer.Name, bouncer.AuthType) 132 return nil 133 } 134 135 return bouncer 136 } 137 138 func (a *APIKey) MiddlewareFunc() gin.HandlerFunc { 139 return func(c *gin.Context) { 140 var bouncer *ent.Bouncer 141 142 clientIP := c.ClientIP() 143 144 logger := log.WithFields(log.Fields{ 145 "ip": clientIP, 146 }) 147 148 if c.Request.TLS != nil && len(c.Request.TLS.PeerCertificates) > 0 { 149 bouncer = a.authTLS(c, logger) 150 } else { 151 bouncer = a.authPlain(c, logger) 152 } 153 154 if bouncer == nil { 155 c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) 156 c.Abort() 157 158 return 159 } 160 161 logger = logger.WithFields(log.Fields{ 162 "name": bouncer.Name, 163 }) 164 165 if bouncer.IPAddress == "" { 166 if err := a.DbClient.UpdateBouncerIP(clientIP, bouncer.ID); err != nil { 167 logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err) 168 c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) 169 c.Abort() 170 171 return 172 } 173 } 174 175 // Don't update IP on HEAD request, as it's used by the appsec to check the validity of the API key provided 176 if bouncer.IPAddress != clientIP && bouncer.IPAddress != "" && c.Request.Method != http.MethodHead { 177 log.Warningf("new IP address detected for bouncer '%s': %s (old: %s)", bouncer.Name, clientIP, bouncer.IPAddress) 178 179 if err := a.DbClient.UpdateBouncerIP(clientIP, bouncer.ID); err != nil { 180 logger.Errorf("Failed to update ip address for '%s': %s\n", bouncer.Name, err) 181 c.JSON(http.StatusForbidden, gin.H{"message": "access forbidden"}) 182 c.Abort() 183 184 return 185 } 186 } 187 188 useragent := strings.Split(c.Request.UserAgent(), "/") 189 if len(useragent) != 2 { 190 logger.Warningf("bad user agent '%s'", c.Request.UserAgent()) 191 useragent = []string{c.Request.UserAgent(), "N/A"} 192 } 193 194 if bouncer.Version != useragent[1] || bouncer.Type != useragent[0] { 195 if err := a.DbClient.UpdateBouncerTypeAndVersion(useragent[0], useragent[1], bouncer.ID); err != nil { 196 logger.Errorf("failed to update bouncer version and type: %s", err) 197 c.JSON(http.StatusForbidden, gin.H{"message": "bad user agent"}) 198 c.Abort() 199 200 return 201 } 202 } 203 204 c.Set(BouncerContextKey, bouncer) 205 } 206 }