Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add asymmetric JWT signing #16010

Merged
merged 16 commits into from
Jun 17, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Updated documentation.
KN4CK3R committed May 30, 2021
commit 2531f4b356e2cae7be7c94148e743548ce7958d8
8 changes: 5 additions & 3 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
@@ -350,7 +350,7 @@ relation to port exhaustion.
- `ISSUE_INDEXER_PATH`: **indexers/issues.bleve**: Index file used for issue search; available when ISSUE_INDEXER_TYPE is bleve and elasticsearch.
- The next 4 configuration values are deprecated and should be set in `queue.issue_indexer` however are kept for backwards compatibility:
- `ISSUE_INDEXER_QUEUE_TYPE`: **levelqueue**: Issue indexer queue, currently supports:`channel`, `levelqueue`, `redis`.
- `ISSUE_INDEXER_QUEUE_DIR`: **queues/common**: When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this will be the path where the queue will be saved. (Previously this was `indexers/issues.queue`.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per discussion in chat, these changed lines are fine due to the fact that it cleans up line endings (shakes fist at windows)

- `ISSUE_INDEXER_QUEUE_DIR`: **queues/common**: When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this will be the path where the queue will be saved. (Previously this was `indexers/issues.queue`.)
- `ISSUE_INDEXER_QUEUE_CONN_STR`: **addrs=127.0.0.1:6379 db=0**: When `ISSUE_INDEXER_QUEUE_TYPE` is `redis`, this will store the redis connection string. When `ISSUE_INDEXER_QUEUE_TYPE` is `levelqueue`, this is a directory or additional options of the form `leveldb://path/to/db?option=value&....`, and overrides `ISSUE_INDEXER_QUEUE_DIR`.
- `ISSUE_INDEXER_QUEUE_BATCH_NUMBER`: **20**: Batch queue number.

@@ -370,7 +370,7 @@ relation to port exhaustion.
## Queue (`queue` and `queue.*`)

- `TYPE`: **persistable-channel**: General queue type, currently support: `persistable-channel` (uses a LevelDB internally), `channel`, `level`, `redis`, `dummy`
- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`common`**. (Previously each queue would default to `DATADIR/`**`name`**.)
- `DATADIR`: **queues/**: Base DataDir for storing persistent and level queues. `DATADIR` for individual queues can be set in `queue.name` sections but will default to `DATADIR/`**`common`**. (Previously each queue would default to `DATADIR/`**`name`**.)
- `LENGTH`: **20**: Maximal queue size before channel queues block
- `BATCH_LENGTH`: **20**: Batch data before passing to the handler
- `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. Options can be set using query params. Similarly LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**, and will override `DATADIR`
@@ -851,7 +851,9 @@ NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take ef
- `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds
- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 refresh token in hours
- `INVALIDATE_REFRESH_TOKENS`: **false**: Check if refresh token has already been used
- `JWT_SECRET`: **\<empty\>**: OAuth2 authentication secret for access and refresh tokens, change this a unique string.
- `JWT_SIGNING_ALGORITHM`: **RS256**: Algorithm used to sign OAuth2 tokens. Valid values: \[`HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`\]
- `JWT_SECRET`: **\<empty\>**: OAuth2 authentication secret for access and refresh tokens, change this to a unique string. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `HS256`, `HS384` or `HS512`.
- `JWT_SIGNING_PRIVATE_KEY_FILE`: **jwt/private.pem**: Private key file path used to sign OAuth2 tokens. The path is relative to `CUSTOM_PATH`. This setting is only needed if `JWT_SIGNING_ALGORITHM` is set to `RS256`, `RS384`, `RS512`, `ES256`, `ES384` or `ES512`. The file must contain a RSA or ECDSA private key in the PKCS8 format.
- `MAX_TOKEN_LENGTH`: **32767**: Maximum length of token/cookie to accept from OAuth2 provider

## i18n (`i18n`)
11 changes: 7 additions & 4 deletions docs/content/doc/developers/oauth2-provider.md
Original file line number Diff line number Diff line change
@@ -23,10 +23,13 @@ Gitea supports acting as an OAuth2 provider to allow third party applications to

## Endpoints

| Endpoint | URL |
| ---------------------- | --------------------------- |
| Authorization Endpoint | `/login/oauth/authorize` |
| Access Token Endpoint | `/login/oauth/access_token` |
| Endpoint | URL |
| ------------------------ | ----------------------------------- |
| OpenID Connect Discovery | `/.well-known/openid-configuration` |
| Authorization Endpoint | `/login/oauth/authorize` |
| Access Token Endpoint | `/login/oauth/access_token` |
| OpenID Connect UserInfo | `/login/oauth/userinfo` |
| JSON Web Key Set | `/login/oauth/keys` |

## Supported OAuth2 Grants

21 changes: 15 additions & 6 deletions modules/auth/oauth2/jwtsigningkey.go
Original file line number Diff line number Diff line change
@@ -69,7 +69,10 @@ func (key hmacSingingKey) VerifyKey() interface{} {
}

func (key hmacSingingKey) ToJWK() (map[string]string, error) {
return map[string]string{}, nil
return map[string]string{
"kty": "oct",
"alg": key.SigningMethod().Alg(),
}, nil
}

type rsaSingingKey struct {
@@ -149,6 +152,8 @@ func (key ecdsaSingingKey) ToJWK() (map[string]string, error) {
}, nil
}

// createPublicKeyFingerprint creates a fingerprint of the given key.
// The fingerprint is the sha256 sum of the PKIX structure of the key.
func createPublicKeyFingerprint(key interface{}) ([]byte, error) {
bytes, err := x509.MarshalPKIXPublicKey(key)
if err != nil {
@@ -214,7 +219,8 @@ func CreateJWTSingingKey(algorithm string, key interface{}) (JWTSigningKey, erro
var DefaultSigningKey JWTSigningKey

// InitSigningKey creates the default signing key from settings or creates a random key.
func InitSigningKey() (err error) {
func InitSigningKey() error {
var err error
var key interface{}

switch setting.OAuth2.JWTSigningAlgorithm {
@@ -243,20 +249,21 @@ func InitSigningKey() (err error) {
}

if err != nil {
log.Error("Error while loading or creating symmetric key: %v", err)
return
return fmt.Errorf("Error while loading or creating symmetric key: %v", err)
}

signingKey, err := CreateJWTSingingKey(setting.OAuth2.JWTSigningAlgorithm, key)
if err != nil {
return
return err
}

DefaultSigningKey = signingKey

return
return nil
}

// loadOrCreateSymmetricKey checks if the configured secret is valid.
// If it is not valid a new secret is created and saved in the configuration file.
func loadOrCreateSymmetricKey() (interface{}, error) {
key := make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(key, []byte(setting.OAuth2.JWTSecretBase64))
@@ -276,6 +283,8 @@ func loadOrCreateSymmetricKey() (interface{}, error) {
return key, nil
}

// loadOrCreateAsymmetricKey checks if the configured private key exists.
// If it does not exist a new random key gets generated and saved on the configured path.
func loadOrCreateAsymmetricKey() (interface{}, error) {
keyPath := setting.OAuth2.JWTSigningPrivateKeyFile

4 changes: 2 additions & 2 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
@@ -361,8 +361,8 @@ var (
AccessTokenExpirationTime int64
RefreshTokenExpirationTime int64
InvalidateRefreshTokens bool
JWTSecretBase64 string `ini:"JWT_SECRET"`
JWTSigningAlgorithm string `ini:"JWT_SIGNING_ALGORITHM"`
JWTSecretBase64 string `ini:"JWT_SECRET"`
JWTSigningPrivateKeyFile string `ini:"JWT_SIGNING_PRIVATE_KEY_FILE"`
MaxTokenLength int
}{
@@ -787,7 +787,7 @@ func NewContext() {
}

if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(CustomPath, OAuth2.JWTSigningPrivateKeyFile)
}

sec = Cfg.Section("admin")