IAM: Add validation for new tokens with no expiration when token_expiration_day_limit is set (#114166)
* do not create sa with no expiration when day limit is set * disable No expiration option if day limit is set * make i18n-extract * run prettier * address feedback
This commit is contained in:
@@ -157,6 +157,10 @@ func (api *ServiceAccountsAPI) CreateToken(c *contextmodel.ReqContext) response.
|
||||
}
|
||||
|
||||
if api.cfg.SATokenExpirationDayLimit > 0 {
|
||||
if cmd.SecondsToLive == 0 {
|
||||
return response.Error(http.StatusBadRequest, "Cannot create token with no expiration date when service_accounts.token_expiration_day_limit is set", nil)
|
||||
}
|
||||
|
||||
dayExpireLimit := time.Now().Add(time.Duration(api.cfg.SATokenExpirationDayLimit) * time.Hour * 24).Truncate(24 * time.Hour)
|
||||
expirationDate := time.Now().Add(time.Duration(cmd.SecondsToLive) * time.Second).Truncate(24 * time.Hour)
|
||||
if expirationDate.After(dayExpireLimit) {
|
||||
|
||||
@@ -60,14 +60,15 @@ func TestServiceAccountsAPI_ListTokens(t *testing.T) {
|
||||
|
||||
func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
||||
type TestCase struct {
|
||||
desc string
|
||||
id int64
|
||||
body string
|
||||
permissions []accesscontrol.Permission
|
||||
tokenTTL int64
|
||||
expectedErr error
|
||||
expectedAPIKey *apikey.APIKey
|
||||
expectedCode int
|
||||
desc string
|
||||
id int64
|
||||
body string
|
||||
permissions []accesscontrol.Permission
|
||||
tokenTTL int64
|
||||
tokenExpirationDayLimit int
|
||||
expectedErr error
|
||||
expectedAPIKey *apikey.APIKey
|
||||
expectedCode int
|
||||
}
|
||||
|
||||
tests := []TestCase{
|
||||
@@ -105,12 +106,58 @@ func TestServiceAccountsAPI_CreateToken(t *testing.T) {
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "should not be able to create token for service account if max ttl is configured and exceeds the limit",
|
||||
id: 1,
|
||||
body: `{"name": "test", "secondsToLive": 11000}`,
|
||||
tokenTTL: 10000,
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "should be able to create token for service account if max ttl is configured and within the limit",
|
||||
id: 1,
|
||||
body: `{"name": "test", "secondsToLive": 5000}`,
|
||||
tokenTTL: 10000,
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedAPIKey: &apikey.APIKey{},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "should not be able to create token for service account if token expiration day limit is configured but not set in body",
|
||||
id: 1,
|
||||
body: `{"name": "test"}`,
|
||||
tokenTTL: -1,
|
||||
tokenExpirationDayLimit: 30,
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "should not be able to create token for service account if token expiration day limit is configured and exceeds the limit",
|
||||
id: 1,
|
||||
body: `{"name": "test", "secondsToLive": 340000}`, // 340000 seconds is > 3 days
|
||||
tokenTTL: -1,
|
||||
tokenExpirationDayLimit: 3,
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "should be able to create token for service account if token expiration day limit is configured and within the limit",
|
||||
id: 1,
|
||||
body: `{"name": "test", "secondsToLive": 250000}`, // 250000 seconds is almost 3 days
|
||||
tokenTTL: -1,
|
||||
tokenExpirationDayLimit: 3,
|
||||
permissions: []accesscontrol.Permission{{Action: serviceaccounts.ActionWrite, Scope: "serviceaccounts:id:1"}},
|
||||
expectedAPIKey: &apikey.APIKey{},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
server := setupTests(t, func(a *ServiceAccountsAPI) {
|
||||
a.cfg.ApiKeyMaxSecondsToLive = tt.tokenTTL
|
||||
a.cfg.SATokenExpirationDayLimit = tt.tokenExpirationDayLimit
|
||||
a.service = &satests.FakeServiceAccountService{
|
||||
ExpectedErr: tt.expectedErr,
|
||||
ExpectedAPIKey: tt.expectedAPIKey,
|
||||
|
||||
@@ -16,10 +16,8 @@ import {
|
||||
useStyles2,
|
||||
} from '@grafana/ui';
|
||||
|
||||
const EXPIRATION_OPTIONS = [
|
||||
{ label: 'No expiration', value: false },
|
||||
{ label: 'Set expiration date', value: true },
|
||||
];
|
||||
const NO_EXPIRATION_OPTION = 'no-expiration';
|
||||
const CUSTOM_EXPIRATION_OPTION = 'custom-expiration';
|
||||
|
||||
export type ServiceAccountToken = {
|
||||
name: string;
|
||||
@@ -44,11 +42,15 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
||||
} else {
|
||||
maxExpirationDate.setDate(8640000000000000);
|
||||
}
|
||||
const defaultExpirationDate = config.tokenExpirationDayLimit !== undefined && config.tokenExpirationDayLimit > 0;
|
||||
|
||||
const isTokenExpirationDayLimitConfigured =
|
||||
config.tokenExpirationDayLimit !== undefined && config.tokenExpirationDayLimit > 0;
|
||||
|
||||
const defaultExpirationOption = isTokenExpirationDayLimitConfigured ? CUSTOM_EXPIRATION_OPTION : NO_EXPIRATION_OPTION;
|
||||
|
||||
const [defaultTokenName, setDefaultTokenName] = useState('');
|
||||
const [newTokenName, setNewTokenName] = useState('');
|
||||
const [isWithExpirationDate, setIsWithExpirationDate] = useState(defaultExpirationDate);
|
||||
const [expirationOption, setExpirationOption] = useState(defaultExpirationOption);
|
||||
const [newTokenExpirationDate, setNewTokenExpirationDate] = useState<Date | string>(tomorrow);
|
||||
const [isExpirationDateValid, setIsExpirationDateValid] = useState(newTokenExpirationDate !== '');
|
||||
const styles = useStyles2(getStyles);
|
||||
@@ -69,14 +71,15 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
||||
const onGenerateToken = () => {
|
||||
onCreateToken({
|
||||
name: newTokenName || defaultTokenName,
|
||||
secondsToLive: isWithExpirationDate ? getSecondsToLive(newTokenExpirationDate) : undefined,
|
||||
secondsToLive:
|
||||
expirationOption === CUSTOM_EXPIRATION_OPTION ? getSecondsToLive(newTokenExpirationDate) : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const onCloseInternal = () => {
|
||||
setNewTokenName('');
|
||||
setDefaultTokenName('');
|
||||
setIsWithExpirationDate(defaultExpirationDate);
|
||||
setExpirationOption(defaultExpirationOption);
|
||||
setNewTokenExpirationDate(tomorrow);
|
||||
setIsExpirationDateValid(newTokenExpirationDate !== '');
|
||||
onClose();
|
||||
@@ -84,6 +87,24 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
||||
|
||||
const modalTitle = !token ? 'Add service account token' : 'Service account token created';
|
||||
|
||||
const getExpirationOptions = () => {
|
||||
const noExpirationDescription = t(
|
||||
'serviceaccounts.create-token-modal.description-no-expiration-disabled',
|
||||
'Cannot create a token with no expiration date when token expiration day limit is configured'
|
||||
);
|
||||
return [
|
||||
{
|
||||
label: t('serviceaccounts.create-token-modal.label-no-expiration', 'No expiration'),
|
||||
value: NO_EXPIRATION_OPTION,
|
||||
description: isTokenExpirationDayLimitConfigured ? noExpirationDescription : undefined,
|
||||
},
|
||||
{
|
||||
label: t('serviceaccounts.create-token-modal.label-set-expiration-date', 'Set expiration date'),
|
||||
value: CUSTOM_EXPIRATION_OPTION,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} title={modalTitle} onDismiss={onCloseInternal} className={styles.modal}>
|
||||
{!token ? (
|
||||
@@ -109,13 +130,14 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
||||
</Field>
|
||||
<Field label={t('serviceaccounts.create-token-modal.label-expiration', 'Expiration')}>
|
||||
<RadioButtonGroup
|
||||
options={EXPIRATION_OPTIONS}
|
||||
value={isWithExpirationDate}
|
||||
onChange={setIsWithExpirationDate}
|
||||
options={getExpirationOptions()}
|
||||
disabledOptions={isTokenExpirationDayLimitConfigured ? [NO_EXPIRATION_OPTION] : []}
|
||||
value={expirationOption}
|
||||
onChange={setExpirationOption}
|
||||
size="md"
|
||||
/>
|
||||
</Field>
|
||||
{isWithExpirationDate && (
|
||||
{expirationOption === CUSTOM_EXPIRATION_OPTION && (
|
||||
<Field label={t('serviceaccounts.create-token-modal.label-expiration-date', 'Expiration date')}>
|
||||
<DatePickerWithInput
|
||||
onChange={onExpirationDateChange}
|
||||
@@ -127,7 +149,10 @@ export const CreateTokenModal = ({ isOpen, token, serviceAccountLogin, onCreateT
|
||||
</Field>
|
||||
)}
|
||||
<Modal.ButtonRow>
|
||||
<Button onClick={onGenerateToken} disabled={isWithExpirationDate && !isExpirationDateValid}>
|
||||
<Button
|
||||
onClick={onGenerateToken}
|
||||
disabled={expirationOption === CUSTOM_EXPIRATION_OPTION && !isExpirationDateValid}
|
||||
>
|
||||
<Trans i18nKey="serviceaccounts.create-token-modal.generate-token">Generate token</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
|
||||
@@ -12543,11 +12543,14 @@
|
||||
"copy-clipboard": "Copy to clipboard",
|
||||
"copy-to-clipboard-and-close": "Copy to clipboard and close",
|
||||
"description-name-to-easily-identify-the-token": "Name to easily identify the token",
|
||||
"description-no-expiration-disabled": "Cannot create a token with no expiration date when token expiration day limit is configured",
|
||||
"description-token": "Copy the token now as you will not be able to see it again. Losing a token requires creating a new one.",
|
||||
"generate-token": "Generate token",
|
||||
"label-display-name": "Display name",
|
||||
"label-expiration": "Expiration",
|
||||
"label-expiration-date": "Expiration date",
|
||||
"label-no-expiration": "No expiration",
|
||||
"label-set-expiration-date": "Set expiration date",
|
||||
"label-token": "Token"
|
||||
},
|
||||
"get-actions-cell": {
|
||||
|
||||
Reference in New Issue
Block a user