Unverified Commit 438da707 authored by akshya96's avatar akshya96 Committed by GitHub
Browse files

Merge branch 'release/1.8.x' into backport/Vault-4010FixHelpPanic/deeply-nice-koala

parents b5bc6d8c ceef6d5d
Branches unavailable
No related merge requests found
Showing with 336 additions and 34 deletions
+336 -34
......@@ -36,7 +36,7 @@ jobs:
- run:
command: |
case "$CIRCLE_BRANCH" in
main|ui/*|release/*|merge*) ;;
main|ui/*|backport/ui/*|release/*|merge*) ;;
*) # If the branch being tested doesn't match one of the above patterns,
# we don't need to run test-ui and can abort the job.
circleci-agent step halt
......
......@@ -6,7 +6,7 @@ steps:
name: Check branch name
command: |
case "$CIRCLE_BRANCH" in
main|ui/*|release/*|merge*) ;;
main|ui/*|backport/ui/*|release/*|merge*) ;;
*) # If the branch being tested doesn't match one of the above patterns,
# we don't need to run test-ui and can abort the job.
circleci-agent step halt
......
url_docker_registry_dockerhub = "https://hub.docker.com/r/hashicorp/vault"
url_docker_registry_ecr = "https://gallery.ecr.aws/hashicorp/vault"
url_license = "https://github.com/hashicorp/vault/blob/main/LICENSE"
url_project_website = "https://www.vaultproject.io/"
url_source_repository = "https://github.com/hashicorp/vault"
url_release_notes = "https://www.vaultproject.io/docs/release-notes"
......@@ -2,6 +2,7 @@ package approle
import (
"context"
"strings"
"testing"
"time"
......@@ -264,6 +265,26 @@ func TestAppRole_RoleLogin(t *testing.T) {
if resp.Auth.Period != period {
t.Fatalf("expected period value of %d in the response, got: %s", period, resp.Auth.Period)
}
// Test input validation with secret_id that exceeds max length
loginData["secret_id"] = strings.Repeat("a", maxHmacInputLength+1)
loginReq = &logical.Request{
Operation: logical.UpdateOperation,
Path: "login",
Storage: storage,
Data: loginData,
Connection: &logical.Connection{
RemoteAddr: "127.0.0.1",
},
}
loginResp, err = b.HandleRequest(context.Background(), loginReq)
expectedErr := "failed to create HMAC of secret_id"
if loginResp != nil || err == nil || !strings.Contains(err.Error(), expectedErr) {
t.Fatalf("expected login test to fail with error %q, resp: %#v, err: %v", expectedErr, loginResp, err)
}
}
func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Request {
......
......@@ -91,12 +91,19 @@ func verifyCIDRRoleSecretIDSubset(secretIDCIDRs []string, roleBoundCIDRList []st
return nil
}
const maxHmacInputLength = 1024
// Creates a SHA256 HMAC of the given 'value' using the given 'key' and returns
// a hex encoded string.
func createHMAC(key, value string) (string, error) {
if key == "" {
return "", fmt.Errorf("invalid HMAC key")
}
if len(value) > maxHmacInputLength {
return "", fmt.Errorf("value is longer than maximum of %d bytes", maxHmacInputLength)
}
hm := hmac.New(sha256.New, []byte(key))
hm.Write([]byte(value))
return hex.EncodeToString(hm.Sum(nil)), nil
......
```release-note:bug
core: Fix panic caused by parsing policies with empty slice values.
```
```release-note:bug
core: Fix panic caused by parsing JSON integers for fields defined as comma-delimited strings
```
```release-note:bug
cli: Fix panic caused by parsing key=value fields whose value is a single backslash
```
```release-note:bug
ui: Fixes issue with correct auth method not selected when logging out from OIDC or JWT methods
```
\ No newline at end of file
```release-note:bug
auth/approle: Add maximum length for input values that result in SHA56 HMAC calculation
```
......@@ -99,6 +99,12 @@ func TestKVPutCommand(t *testing.T) {
"created_time",
0,
},
{
"v2_single_value_backslash",
[]string{"kv/write/foo", "foo=\\"},
"created_time",
0,
},
}
for _, tc := range cases {
......
......@@ -126,6 +126,11 @@ func getRuleInfo(rule map[string]interface{}) (data ruleInfo, err error) {
if err != nil {
return data, fmt.Errorf("unable to get rule data: %w", err)
}
if len(slice) == 0 {
return data, fmt.Errorf("rule info cannot be empty")
}
data = ruleInfo{
ruleType: key,
data: slice[0],
......
......@@ -297,6 +297,15 @@ func TestParser_ParsePolicy(t *testing.T) {
expected: StringGenerator{},
expectErr: true,
},
"config value with empty slice": {
registry: defaultRuleNameMapping,
rawConfig: `
rule {
n = []
}`,
expected: StringGenerator{},
expectErr: true,
},
}
for name, test := range tests {
......
......@@ -91,7 +91,7 @@ func (b *Builder) add(raw string) error {
}
value = string(contents)
} else if value[0] == '\\' && value[1] == '@' {
} else if len(value) >= 2 && value[0] == '\\' && value[1] == '@' {
value = value[1:]
} else if value == "-" {
if b.Stdin == nil {
......
......@@ -41,6 +41,23 @@ func TestBuilder_escapedAt(t *testing.T) {
}
}
func TestBuilder_singleBackslash(t *testing.T) {
var b Builder
err := b.Add("foo=bar", "bar=\\")
if err != nil {
t.Fatalf("err: %s", err)
}
expected := map[string]interface{}{
"foo": "bar",
"bar": "\\",
}
actual := b.Map()
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestBuilder_stdin(t *testing.T) {
var b Builder
b.Stdin = bytes.NewBufferString("baz")
......
package framework
import (
"encoding/json"
"net/http"
"reflect"
"testing"
......@@ -855,6 +856,18 @@ func TestFieldDataGet(t *testing.T) {
false,
},
"comma string slice type, single JSON number value": {
map[string]*FieldSchema{
"foo": {Type: TypeCommaStringSlice},
},
map[string]interface{}{
"foo": json.Number("123"),
},
"foo",
[]string{"123"},
false,
},
"type kv pair, not supplied": {
map[string]*FieldSchema{
"foo": {Type: TypeKVPairs},
......
......@@ -248,6 +248,11 @@ func ParseString(in interface{}) (string, error) {
}
func ParseCommaStringSlice(in interface{}) ([]string, error) {
jsonIn, ok := in.(json.Number)
if ok {
in = jsonIn.String()
}
rawString, ok := in.(string)
if ok && rawString == "" {
return []string{}, nil
......
......@@ -2,6 +2,7 @@ package parseutil
import (
"encoding/json"
"math/cmplx"
"testing"
"time"
)
......@@ -282,3 +283,182 @@ func Test_ParseBool(t *testing.T) {
t.Fatal("wrong output")
}
}
func equalStringSlice(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func Test_ParseCommaStringSlice(t *testing.T) {
cases := []struct {
name string
inp interface{}
expected []string
valid bool
}{
{
"nil",
nil,
[]string{},
true,
},
{
"empty string",
"",
[]string{},
true,
},
{
"string without commas",
"foo",
[]string{"foo"},
true,
},
{
"comma-separated string",
"foo,bar,baz",
[]string{"foo", "bar", "baz"},
true,
},
{
"comma-separated string with trim",
" foo , bar ,baz ",
[]string{"foo", "bar", "baz"},
true,
},
{
"json number",
json.Number("123"),
[]string{"123"},
true,
},
{
"int",
1,
[]string{"1"},
true,
},
{
"float",
5.5,
[]string{"5.5"},
true,
},
{
"rune",
'a',
[]string{"97"},
true,
},
{
"bool",
true,
[]string{"1"},
true,
},
{
"byte",
byte(10),
[]string{"10"},
true,
},
{
"complex",
cmplx.Sqrt(-1),
nil,
false,
},
{
"time",
time.Now(),
nil,
false,
},
{
"string slice",
[]string{"foo", "bar", "baz"},
[]string{"foo", "bar", "baz"},
true,
},
{
"json number slice",
[]json.Number{json.Number("1"), json.Number("2")},
[]string{"1", "2"},
true,
},
{
"int slice",
[]int{1, 2, 3},
[]string{"1", "2", "3"},
true,
},
{
"float slice",
[]float64{1.1, 1.2, 1.3},
[]string{"1.1", "1.2", "1.3"},
true,
},
{
"rune slice",
[]rune{'a', 'b', 'c'},
[]string{"97", "98", "99"},
true,
},
{
"bool slice",
[]bool{true, false, true},
[]string{"1", "0", "1"},
true,
},
{
"complex slice",
[]complex128{cmplx.Sqrt(-1)},
nil,
false,
},
{
"map",
map[string]interface{}{"foo": "bar"},
nil,
false,
},
{
"struct",
struct{ name string }{"foo"},
nil,
false,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
outp, err := ParseCommaStringSlice(tc.inp)
if tc.valid && err != nil {
t.Errorf("failed to parse: %v. err: %v", tc.inp, err)
}
if !tc.valid && err == nil {
t.Errorf("no error for: %v", tc.inp)
}
if !equalStringSlice(outp, tc.expected) {
t.Errorf("input %v parsed as %v, expected %v", tc.inp, outp, tc.expected)
}
})
}
}
......@@ -109,6 +109,18 @@ export default Component.extend(DEFAULTS, {
this.setProperties(DEFAULTS);
},
getAuthBackend(type) {
const { wrappedToken, methods, selectedAuth, selectedAuthIsPath: keyIsPath } = this;
const selected = type || selectedAuth;
if (!methods && !wrappedToken) {
return {};
}
if (keyIsPath) {
return methods.findBy('path', selected);
}
return BACKENDS.findBy('type', selected);
},
selectedAuthIsPath: match('selectedAuth', /\/$/),
selectedAuthBackend: computed(
'wrappedToken',
......@@ -116,19 +128,12 @@ export default Component.extend(DEFAULTS, {
'methods.[]',
'selectedAuth',
'selectedAuthIsPath',
function() {
let { wrappedToken, methods, selectedAuth, selectedAuthIsPath: keyIsPath } = this;
if (!methods && !wrappedToken) {
return {};
}
if (keyIsPath) {
return methods.findBy('path', selectedAuth);
}
return BACKENDS.findBy('type', selectedAuth);
function () {
return this.getAuthBackend();
}
),
providerName: computed('selectedAuthBackend.type', function() {
providerName: computed('selectedAuthBackend.type', function () {
if (!this.selectedAuthBackend) {
return;
}
......@@ -142,22 +147,24 @@ export default Component.extend(DEFAULTS, {
cspErrorText: `This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`,
allSupportedMethods: computed('methodsToShow', 'hasMethodsWithPath', function() {
allSupportedMethods: computed('methodsToShow', 'hasMethodsWithPath', function () {
let hasMethodsWithPath = this.hasMethodsWithPath;
let methodsToShow = this.methodsToShow;
return hasMethodsWithPath ? methodsToShow.concat(BACKENDS) : methodsToShow;
}),
hasMethodsWithPath: computed('methodsToShow', function() {
hasMethodsWithPath: computed('methodsToShow', function () {
return this.methodsToShow.isAny('path');
}),
methodsToShow: computed('methods', function() {
methodsToShow: computed('methods', function () {
let methods = this.methods || [];
let shownMethods = methods.filter(m => BACKENDS.find(b => b.type.toLowerCase() === m.type.toLowerCase()));
let shownMethods = methods.filter((m) =>
BACKENDS.find((b) => b.type.toLowerCase() === m.type.toLowerCase())
);
return shownMethods.length ? shownMethods : BACKENDS;
}),
unwrapToken: task(function*(token) {
unwrapToken: task(function* (token) {
// will be using the Token Auth Method, so set it here
this.set('selectedAuth', 'token');
let adapter = this.store.adapterFor('tools');
......@@ -170,7 +177,7 @@ export default Component.extend(DEFAULTS, {
}
}).withTestWaiter(),
fetchMethods: task(function*() {
fetchMethods: task(function* () {
let store = this.store;
try {
let methods = yield store.findAll('auth-method', {
......@@ -180,7 +187,7 @@ export default Component.extend(DEFAULTS, {
});
this.set(
'methods',
methods.map(m => {
methods.map((m) => {
const method = m.serialize({ includeId: true });
return {
...method,
......@@ -202,7 +209,7 @@ export default Component.extend(DEFAULTS, {
this.set('loading', false);
let errors;
if (e.errors) {
errors = e.errors.map(error => {
errors = e.errors.map((error) => {
if (error.detail) {
return error.detail;
}
......@@ -215,13 +222,21 @@ export default Component.extend(DEFAULTS, {
this.set('error', `${message}${errors.join('.')}`);
},
authenticate: task(function*(backendType, data) {
let clusterId = this.cluster.id;
authenticate: task(function* (backendType, data) {
const {
selectedAuth,
cluster: { id: clusterId },
} = this;
try {
if (backendType === 'okta') {
this.delayAuthMessageReminder.perform();
}
let authResponse = yield this.auth.authenticate({ clusterId, backend: backendType, data });
let authResponse = yield this.auth.authenticate({
clusterId,
backend: backendType,
data,
selectedAuth,
});
let { isRoot, namespace } = authResponse;
let transition;
......@@ -248,7 +263,7 @@ export default Component.extend(DEFAULTS, {
}
}).withTestWaiter(),
delayAuthMessageReminder: task(function*() {
delayAuthMessageReminder: task(function* () {
if (Ember.testing) {
this.showLoading = true;
yield timeout(0);
......@@ -272,9 +287,12 @@ export default Component.extend(DEFAULTS, {
this.setProperties({
error: null,
});
let backend = this.selectedAuthBackend || {};
// if callback from oidc or jwt we have a token at this point
let backend = ['oidc', 'jwt'].includes(this.selectedAuth)
? this.getAuthBackend('token')
: this.selectedAuthBackend || {};
let backendMeta = BACKENDS.find(
b => (b.type || '').toLowerCase() === (backend.type || '').toLowerCase()
(b) => (b.type || '').toLowerCase() === (backend.type || '').toLowerCase()
);
let attributes = (backendMeta || {}).formAttributes || [];
......
......@@ -29,6 +29,7 @@ export default Component.extend({
onNamespace() {},
didReceiveAttrs() {
this._super();
let { oldSelectedAuthPath, selectedAuthPath } = this;
let shouldDebounce = !oldSelectedAuthPath && !selectedAuthPath;
if (oldSelectedAuthPath !== selectedAuthPath) {
......@@ -44,7 +45,7 @@ export default Component.extend({
// Assumes authentication using OIDC until it's known that the mount is
// configured for JWT authentication via static keys, JWKS, or OIDC discovery.
isOIDC: computed('errorMessage', function() {
isOIDC: computed('errorMessage', function () {
return this.errorMessage !== ERROR_JWT_LOGIN;
}),
......@@ -52,7 +53,7 @@ export default Component.extend({
return this.window || window;
},
fetchRole: task(function*(roleName, options = { debounce: true }) {
fetchRole: task(function* (roleName, options = { debounce: true }) {
if (options.debounce) {
this.onRoleName(roleName);
// debounce
......@@ -82,7 +83,7 @@ export default Component.extend({
this.onError(err);
},
prepareForOIDC: task(function*(oidcWindow) {
prepareForOIDC: task(function* (oidcWindow) {
const thisWindow = this.getWindow();
// show the loading animation in the parent
this.onLoading(true);
......@@ -104,7 +105,7 @@ export default Component.extend({
}
}),
watchPopup: task(function*(oidcWindow) {
watchPopup: task(function* (oidcWindow) {
while (true) {
yield timeout(WAIT_TIME);
if (!oidcWindow || oidcWindow.closed) {
......@@ -113,7 +114,7 @@ export default Component.extend({
}
}),
watchCurrent: task(function*(oidcWindow) {
watchCurrent: task(function* (oidcWindow) {
// when user is about to change pages, close the popup window
yield waitForEvent(this.getWindow(), 'beforeunload');
oidcWindow.close();
......@@ -125,7 +126,7 @@ export default Component.extend({
oidcWindow.close();
},
exchangeOIDC: task(function*(oidcState, oidcWindow) {
exchangeOIDC: task(function* (oidcState, oidcWindow) {
if (oidcState === null || oidcState === undefined) {
return;
}
......@@ -163,7 +164,6 @@ export default Component.extend({
return this.handleOIDCError(e);
}
let token = resp.auth.client_token;
this.onSelectedAuth('token');
this.onToken(token);
yield this.onSubmit();
}),
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment