debian-forge-composer/cmd/osbuild-mock-openid-provider/main.go
Brian C. Lane 826e9d8cc6 osbuild-composer: Set ReadHeaderTimeout to 5s
This satisfies the linter complaint about potential Slowloris attack
where headers are read slowly in an attempt to DoS the server.

The uses of ListenAndServe are only for testing purposes and are not run
in the production server so ignore the lint errors in
osbuild-mock-openid-provider.
2022-09-15 03:57:40 -07:00

158 lines
3.7 KiB
Go

package main
import (
"encoding/base64"
"encoding/json"
"flag"
"io/ioutil"
"log"
"math/big"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
)
// Implements /certs and /token
func main() {
var addr string
var rsaPubPem string
var rsaPem string
var tlsCert string
var tlsKey string
var tokenExpires int
var tokenScope string
flag.StringVar(&addr, "a", "localhost:8080", "Address to serve on")
flag.StringVar(&rsaPubPem, "rsaPubPem", "", "rsa pubkey in pem format (path)")
flag.StringVar(&rsaPem, "rsaPem", "", "rsa privkey in pem format (path)")
flag.StringVar(&tlsCert, "cert", "", "tls cert")
flag.StringVar(&tlsKey, "key", "", "tls key")
flag.IntVar(&tokenExpires, "expires", 60, "Expiration of the token in seconds (default: 360))")
flag.StringVar(&tokenScope, "scope", "", "The scope of the token (default: not set)")
flag.Parse()
if rsaPubPem == "" || rsaPem == "" {
panic("path to rsa keys needed")
}
mux := http.NewServeMux()
mux.HandleFunc("/certs", func(w http.ResponseWriter, r *http.Request) {
type key struct {
Kid string `json:"kid"`
Kty string `json:"kty"`
Alg string `json:"alg"`
N string `json:"n"`
E string `json:"e"`
}
rsaPubBytes, err := ioutil.ReadFile(rsaPubPem)
if err != nil {
panic(err)
}
pubKey, err := jwt.ParseRSAPublicKeyFromPEM(rsaPubBytes)
if err != nil {
panic(err)
}
k := key{
Kid: "key-id",
Kty: "RSA",
Alg: "RS256",
N: strings.TrimRight(base64.URLEncoding.EncodeToString(pubKey.N.Bytes()), "="),
E: strings.TrimRight(base64.URLEncoding.EncodeToString(big.NewInt(int64(pubKey.E)).Bytes()), "="),
}
type response struct {
Keys []key `json:"keys"`
}
err = json.NewEncoder(w).Encode(response{
Keys: []key{
k,
},
})
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
})
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
type customClaims struct {
Type string `json:"typ"`
ExpiresAt int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
RHOrgID string `json:"rh-org-id"`
jwt.Claims
}
if err := r.ParseForm(); err != nil {
panic(err)
}
var cc customClaims
switch r.Form.Get("grant_type") {
case "refresh_token":
cc = customClaims{
Type: "Bearer",
ExpiresAt: 0,
IssuedAt: time.Now().Unix(),
// Use refresh_token as rh-org-id
RHOrgID: r.Form.Get("refresh_token"),
}
case "client_credentials":
cc = customClaims{
Type: "Bearer",
ExpiresAt: 0,
IssuedAt: time.Now().Unix(),
// Use client_secret as rh-org-id
RHOrgID: r.Form.Get("client_secret"),
}
default:
w.WriteHeader(http.StatusBadRequest)
return
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, cc)
token.Header["kid"] = "key-id"
rsaPrivBytes, err := ioutil.ReadFile(rsaPem)
if err != nil {
panic(err)
}
privKey, err := jwt.ParseRSAPrivateKeyFromPEM(rsaPrivBytes)
if err != nil {
panic(err)
}
tokenStr, err := token.SignedString(privKey)
if err != nil {
panic(err)
}
// See https://datatracker.ietf.org/doc/html/rfc6749
type response struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"` // required
ExpiresIn int `json:"expires_in,omitempty"` // lifetime in seconds
Scope string `json:"scope,omitempty"`
}
err = json.NewEncoder(w).Encode(response{
AccessToken: tokenStr,
TokenType: "Bearer",
ExpiresIn: tokenExpires,
Scope: tokenScope,
})
if err != nil {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
})
//nolint:gosec
if tlsCert != "" && tlsKey != "" {
log.Fatal(http.ListenAndServeTLS(addr, tlsCert, tlsKey, mux))
} else {
log.Fatal(http.ListenAndServe(addr, mux))
}
}