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

x-pack/filebeat/input/{cel,httpjson}: fix PEM key validation #38405

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ fields added to events containing the Beats version. {pull}37553[37553]
- Fix duplicated addition of regexp extension in CEL input. {pull}38181[38181]
- Fix the incorrect values generated by the uri_parts processor. {pull}38216[38216]
- Fix HTTPJSON handling of empty object bodies in POST requests. {issue}33961[33961] {pull}38290[38290]
- Fix PEM key validation for CEL and HTTPJSON inputs. {pull}38405[38405]

*Heartbeat*

Expand Down
6 changes: 4 additions & 2 deletions x-pack/filebeat/input/cel/config_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package cel

import (
"context"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -341,7 +340,10 @@ func (o *oAuth2Config) validateOktaProvider() error {
}
// jwk_pem
if o.OktaJWKPEM != "" {
_, err := x509.ParsePKCS1PrivateKey([]byte(o.OktaJWKPEM))
_, err := pemPKCS8PrivateKey([]byte(o.OktaJWKPEM))
if err != nil {
return fmt.Errorf("okta validation error: %w", err)
}
return err
}
// jwk_file
Expand Down
20 changes: 14 additions & 6 deletions x-pack/filebeat/input/cel/config_okta_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/http"
Expand Down Expand Up @@ -160,17 +161,24 @@ func (i *base64int) UnmarshalJSON(b []byte) error {
}

func generateOktaJWTPEM(pemdata string, cnf *oauth2.Config) (string, error) {
blk, rest := pem.Decode([]byte(pemdata))
if rest := bytes.TrimSpace(rest); len(rest) != 0 {
return "", fmt.Errorf("PEM text has trailing data: %s", rest)
}
key, err := x509.ParsePKCS8PrivateKey(blk.Bytes)
key, err := pemPKCS8PrivateKey([]byte(pemdata))
if err != nil {
return "", err
}
return signJWT(cnf, key)
}

func pemPKCS8PrivateKey(pemdata []byte) (any, error) {
blk, rest := pem.Decode(pemdata)
if rest := bytes.TrimSpace(rest); len(rest) != 0 {
return nil, fmt.Errorf("PEM text has trailing data: %d bytes", len(rest))
}
if blk == nil {
return nil, errors.New("no PEM data")
}
return x509.ParsePKCS8PrivateKey(blk.Bytes)
}

// signJWT creates a JWT token using required claims and sign it with the
// private key.
func signJWT(cnf *oauth2.Config, key any) (string, error) {
Expand All @@ -182,7 +190,7 @@ func signJWT(cnf *oauth2.Config, key any) (string, error) {
Expiration(now.Add(time.Hour)).
Build()
if err != nil {
return "", err
return "", fmt.Errorf("failed to create token: %w", err)
}
signedToken, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, key))
if err != nil {
Expand Down
41 changes: 41 additions & 0 deletions x-pack/filebeat/input/cel/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,47 @@ var oAuth2ValidationTests = []struct {
},
},
},
{
name: "okta_successful_pem_oauth2_validation",
input: map[string]interface{}{
"auth.oauth2": map[string]interface{}{
"provider": "okta",
"client.id": "a_client_id",
"token_url": "localhost",
"scopes": []string{"foo"},
"okta.jwk_pem": `
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCOuef3HMRhohVT
5kSoAJgV+atpDjkwTwkOq+ImnbBlv75GaApG90w8VpjXjhqN/1KJmwfyrKiquiMq
OPu+o/672Dys5rUAaWSbT7wRF1GjLDDZrM0GHRdV4DGxM/LKI8I5yE1Mx3EzV+D5
ZLmcRc5U4oEoMwtGpr0zRZ7uUr6a28UQwcUsVIPItc1/9rERlo1WTv8dcaj4ECC3
2Sc0y/F+9XqwJvLd4Uv6ckzP0Sv4tbDA+7jpD9MneAIUiZ4LVj2cwbBd+YRY6jXx
MkevcCSmSX60clBY1cIFkw1DYHqtdHEwAQcQHLGMoi72xRP2qrdzIPsaTKVYoHVo
WA9vADdHAgMBAAECggEAIlx7jjCsztyYyeQsL05FTzUWoWo9NnYwtgmHnshkCXsK
MiUmJEOxZO1sSqj5l6oakupyFWigCspZYPbrFNCiqVK7+NxqQzkccY/WtT6p9uDS
ufUyPwCN96zMCd952lSVlBe3FH8Hr9a+YQxw60CbFjCZ67WuR0opTsi6JKJjJSDb
TQQZ4qJR97D05I1TgfmO+VO7G/0/dDaNHnnlYz0AnOgZPSyvrU2G5cYye4842EMB
ng81xjHD+xp55JNui/xYkhmYspYhrB2KlEjkKb08OInUjBeaLEAgA1r9yOHsfV/3
DQzDPRO9iuqx5BfJhdIqUB1aifrye+sbxt9uMBtUgQKBgQDVdfO3GYT+ZycOQG9P
QtdMn6uiSddchVCGFpk331u6M6yafCKjI/MlJDl29B+8R5sVsttwo8/qnV/xd3cn
pY14HpKAsE4l6/Ciagzoj+0NqfPEDhEzbo8CyArcd7pSxt3XxECAfZe2+xivEPHe
gFO60vSFjFtvlLRMDMOmqX3kYQKBgQCrK1DISyQTnD6/axsgh2/ESOmT7n+JRMx/
YzA7Lxu3zGzUC8/sRDa1C41t054nf5ZXJueYLDSc4kEAPddzISuCLxFiTD2FQ75P
lHWMgsEzQObDm4GPE9cdKOjoAvtAJwbvZcjDa029CDx7aCaDzbNvdmplZ7EUrznR
55U8Wsm8pwKBgBytxTmzZwfbCgdDJvFKNKzpwuCB9TpL+v6Y6Kr2Clfg+26iAPFU
MiWqUUInGGBuamqm5g6jI5sM28gQWeTsvC4IRXyes1Eq+uCHSQax15J/Y+3SSgNT
9kjUYYkvWMwoRcPobRYWSZze7XkP2L8hFJ7EGvAaZGqAWxzgliS9HtnhAoGAONZ/
UqMw7Zoac/Ga5mhSwrj7ZvXxP6Gqzjofj+eKqrOlB5yMhIX6LJATfH6iq7cAMxxm
Fu/G4Ll4oB3o5wACtI3wldV/MDtYfJBtoCTjBqPsfNOsZ9hMvBATlsc2qwzKjsAb
tFhzTevoOYpSD75EcSS/G8Ec2iN9bagatBnpl00CgYBVqAOFZelNfP7dj//lpk8y
EUAw7ABOq0S9wkpFWTXIVPoBQUipm3iAUqGNPmvr/9ShdZC9xeu5AwKram4caMWJ
ExRhcDP1hFM6CdmSkIYEgBKvN9N0O4Lx1ba34gk74Hm65KXxokjJHOC0plO7c7ok
LNV/bIgMHOMoxiGrwyjAhg==
-----END PRIVATE KEY-----
`,
},
},
},
}

func TestConfigOauth2Validation(t *testing.T) {
Expand Down
8 changes: 5 additions & 3 deletions x-pack/filebeat/input/httpjson/config_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package httpjson

import (
"context"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -309,8 +308,11 @@ func (o *oAuth2Config) validateOktaProvider() error {
}
// jwk_pem
if o.OktaJWKPEM != "" {
_, err := x509.ParsePKCS1PrivateKey([]byte(o.OktaJWKPEM))
return err
_, err := pemPKCS8PrivateKey([]byte(o.OktaJWKPEM))
if err != nil {
return fmt.Errorf("okta validation error: %w", err)
}
return nil
}
// jwk_file
if o.OktaJWKFile != "" {
Expand Down
20 changes: 14 additions & 6 deletions x-pack/filebeat/input/httpjson/config_okta_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/http"
Expand Down Expand Up @@ -158,17 +159,24 @@ func (i *base64int) UnmarshalJSON(b []byte) error {
}

func generateOktaJWTPEM(pemdata string, cnf *oauth2.Config) (string, error) {
blk, rest := pem.Decode([]byte(pemdata))
if rest := bytes.TrimSpace(rest); len(rest) != 0 {
return "", fmt.Errorf("PEM text has trailing data: %s", rest)
}
key, err := x509.ParsePKCS8PrivateKey(blk.Bytes)
key, err := pemPKCS8PrivateKey([]byte(pemdata))
if err != nil {
return "", err
}
return signJWT(cnf, key)
}

func pemPKCS8PrivateKey(pemdata []byte) (any, error) {
blk, rest := pem.Decode(pemdata)
if rest := bytes.TrimSpace(rest); len(rest) != 0 {
return nil, fmt.Errorf("PEM text has trailing data: %d bytes", len(rest))
}
if blk == nil {
return nil, errors.New("no PEM data")
}
return x509.ParsePKCS8PrivateKey(blk.Bytes)
}

// signJWT creates a JWT token using required claims and sign it with the private key.
func signJWT(cnf *oauth2.Config, key any) (string, error) {
now := time.Now()
Expand All @@ -179,7 +187,7 @@ func signJWT(cnf *oauth2.Config, key any) (string, error) {
Expiration(now.Add(time.Hour)).
Build()
if err != nil {
return "", err
return "", fmt.Errorf("failed to create token: %w", err)
}
signedToken, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256, key))
if err != nil {
Expand Down
41 changes: 41 additions & 0 deletions x-pack/filebeat/input/httpjson/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,47 @@ func TestConfigOauth2Validation(t *testing.T) {
},
},
},
{
name: "okta successful pem oauth2 validation",
input: map[string]interface{}{
"auth.oauth2": map[string]interface{}{
"provider": "okta",
"client.id": "a_client_id",
"token_url": "localhost",
"scopes": []string{"foo"},
"okta.jwk_pem": `
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCOuef3HMRhohVT
5kSoAJgV+atpDjkwTwkOq+ImnbBlv75GaApG90w8VpjXjhqN/1KJmwfyrKiquiMq
OPu+o/672Dys5rUAaWSbT7wRF1GjLDDZrM0GHRdV4DGxM/LKI8I5yE1Mx3EzV+D5
ZLmcRc5U4oEoMwtGpr0zRZ7uUr6a28UQwcUsVIPItc1/9rERlo1WTv8dcaj4ECC3
2Sc0y/F+9XqwJvLd4Uv6ckzP0Sv4tbDA+7jpD9MneAIUiZ4LVj2cwbBd+YRY6jXx
MkevcCSmSX60clBY1cIFkw1DYHqtdHEwAQcQHLGMoi72xRP2qrdzIPsaTKVYoHVo
WA9vADdHAgMBAAECggEAIlx7jjCsztyYyeQsL05FTzUWoWo9NnYwtgmHnshkCXsK
MiUmJEOxZO1sSqj5l6oakupyFWigCspZYPbrFNCiqVK7+NxqQzkccY/WtT6p9uDS
ufUyPwCN96zMCd952lSVlBe3FH8Hr9a+YQxw60CbFjCZ67WuR0opTsi6JKJjJSDb
TQQZ4qJR97D05I1TgfmO+VO7G/0/dDaNHnnlYz0AnOgZPSyvrU2G5cYye4842EMB
ng81xjHD+xp55JNui/xYkhmYspYhrB2KlEjkKb08OInUjBeaLEAgA1r9yOHsfV/3
DQzDPRO9iuqx5BfJhdIqUB1aifrye+sbxt9uMBtUgQKBgQDVdfO3GYT+ZycOQG9P
QtdMn6uiSddchVCGFpk331u6M6yafCKjI/MlJDl29B+8R5sVsttwo8/qnV/xd3cn
pY14HpKAsE4l6/Ciagzoj+0NqfPEDhEzbo8CyArcd7pSxt3XxECAfZe2+xivEPHe
gFO60vSFjFtvlLRMDMOmqX3kYQKBgQCrK1DISyQTnD6/axsgh2/ESOmT7n+JRMx/
YzA7Lxu3zGzUC8/sRDa1C41t054nf5ZXJueYLDSc4kEAPddzISuCLxFiTD2FQ75P
lHWMgsEzQObDm4GPE9cdKOjoAvtAJwbvZcjDa029CDx7aCaDzbNvdmplZ7EUrznR
55U8Wsm8pwKBgBytxTmzZwfbCgdDJvFKNKzpwuCB9TpL+v6Y6Kr2Clfg+26iAPFU
MiWqUUInGGBuamqm5g6jI5sM28gQWeTsvC4IRXyes1Eq+uCHSQax15J/Y+3SSgNT
9kjUYYkvWMwoRcPobRYWSZze7XkP2L8hFJ7EGvAaZGqAWxzgliS9HtnhAoGAONZ/
UqMw7Zoac/Ga5mhSwrj7ZvXxP6Gqzjofj+eKqrOlB5yMhIX6LJATfH6iq7cAMxxm
Fu/G4Ll4oB3o5wACtI3wldV/MDtYfJBtoCTjBqPsfNOsZ9hMvBATlsc2qwzKjsAb
tFhzTevoOYpSD75EcSS/G8Ec2iN9bagatBnpl00CgYBVqAOFZelNfP7dj//lpk8y
EUAw7ABOq0S9wkpFWTXIVPoBQUipm3iAUqGNPmvr/9ShdZC9xeu5AwKram4caMWJ
ExRhcDP1hFM6CdmSkIYEgBKvN9N0O4Lx1ba34gk74Hm65KXxokjJHOC0plO7c7ok
LNV/bIgMHOMoxiGrwyjAhg==
-----END PRIVATE KEY-----
`,
},
},
},
}

for _, c := range cases {
Expand Down
Loading