Skip to content

Commit

Permalink
chore: Update SSC/SC-SAST/SC-DAST session management
Browse files Browse the repository at this point in the history
fix: No longer require user credentials on SSC, SC-SAST & SC-DAST logout commands (requires SSC 24.2+)
  • Loading branch information
rsenden committed Jul 26, 2024
1 parent c2e66bc commit cb7867b
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
import com.fortify.cli.common.session.cli.mixin.SessionNameMixin;
import com.fortify.cli.common.session.helper.ISessionDescriptor;
import com.fortify.cli.common.session.helper.SessionLogoutException;

import lombok.Getter;
import picocli.CommandLine.Mixin;
Expand All @@ -36,11 +37,15 @@ public JsonNode getJsonNode() {
result = sessionHelper.sessionSummaryAsObjectNode(sessionName);
try {
logout(sessionName, sessionHelper.get(sessionName, false));
} catch (Exception e){
LOG.warn("Logout failed");
LOG.debug("Exception details:", e);
} finally {
getSessionHelper().destroy(sessionName);
} catch (Exception e) {
if ( e instanceof SessionLogoutException && !((SessionLogoutException)e).isDestroySession() ) {
throw e;
} else {
LOG.warn("Logout failed");
LOG.debug("Exception details:", e);
getSessionHelper().destroy(sessionName);
}
}
}
return result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.fortify.cli.common.session.helper;

import lombok.Getter;

public class SessionLogoutException extends RuntimeException {
private static final long serialVersionUID = 1L;
@Getter private boolean destroySession;
public SessionLogoutException(String message, boolean destroySession) {
this(message, null, destroySession);
}

public SessionLogoutException(Throwable cause, boolean destroySession) {
this(null, cause, destroySession);
}

public SessionLogoutException(String message, Throwable cause, boolean destroySession) {
super(message, cause);
this.destroySession = destroySession;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
package com.fortify.cli.sc_dast._common.session.cli.cmd;

import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.rest.unirest.UnexpectedHttpResponseException;
import com.fortify.cli.common.session.cli.cmd.AbstractSessionLogoutCommand;
import com.fortify.cli.common.session.helper.SessionLogoutException;
import com.fortify.cli.sc_dast._common.session.cli.mixin.SCDastSessionLogoutOptions;
import com.fortify.cli.sc_dast._common.session.cli.mixin.SCDastUserCredentialOptions;
import com.fortify.cli.sc_dast._common.session.helper.SCDastSessionDescriptor;
import com.fortify.cli.sc_dast._common.session.helper.SCDastSessionHelper;

Expand All @@ -30,6 +33,17 @@ public class SCDastSessionLogoutCommand extends AbstractSessionLogoutCommand<SCD

@Override
protected void logout(String sessionName, SCDastSessionDescriptor sessionDescriptor) {
sessionDescriptor.logout(logoutOptions.getUserCredentialOptions());
if ( !logoutOptions.isNoRevokeToken() ) {
SCDastUserCredentialOptions userCredentialOptions = logoutOptions.getUserCredentialOptions();
try {
sessionDescriptor.logout(userCredentialOptions);
} catch ( UnexpectedHttpResponseException e ) {
if ( e.getStatus()==403 && userCredentialOptions==null ) {
throw new SessionLogoutException("SSC user credentials or --no-revoke-token option must be specified on SSC versions 23.2 or below", false);
} else {
throw e;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,8 @@
import picocli.CommandLine.Option;

public class SCDastSessionLogoutOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private SSCAuthOptions authOptions = new SSCAuthOptions();

public static class SSCAuthOptions {
@ArgGroup(exclusive = true, multiplicity = "1", order = 2)
@Getter private SSCCredentialOptions credentialOptions = new SSCCredentialOptions();
}

public static class SSCCredentialOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private SCDastUserCredentialOptions userOptions= new SCDastUserCredentialOptions();
@Option(names={"--no-revoke-token"})
@Getter private boolean noRevokeToken;
}

public SCDastUserCredentialOptions getUserCredentialOptions() {
return authOptions.credentialOptions.noRevokeToken ? null : authOptions.credentialOptions.userOptions;
}
@ArgGroup(exclusive = false, multiplicity = "0..1")
@Getter private SCDastUserCredentialOptions userCredentialOptions;
@Option(names={"--no-revoke-token"})
@Getter private boolean noRevokeToken = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public SCDastSessionDescriptor(IUrlConfig sscUrlConfig, IUrlConfig scDastUrlConf

@JsonIgnore
public void logout(IUserCredentialsConfig userCredentialsConfig) {
if ( cachedTokenResponse!=null && userCredentialsConfig!=null ) {
SSCTokenHelper.deleteTokensById(getSscUrlConfig(), userCredentialsConfig, getTokenId());
// We only revoke the token if we generated a token upon login,
// and that token hasn't expired yet.
if ( predefinedToken==null && hasActiveCachedTokenResponse() ) {
SSCTokenHelper.revokeToken(getSscUrlConfig(), userCredentialsConfig, cachedTokenResponse.getData().getToken());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ fcli.sc-dast.session.login.usage.description.0 = This command stores the SSC and
CI/CD pipeline use) as listed below.
fcli.sc-dast.session.login.usage.description.1 = %nWhen logging in with username and password, this \
command will connect to SSC to generate a CIToken; the user password is not stored by fcli. When \
logging in with a pre-generated CIToken, this token will be stored as-is. Note that due to SSC \
limitations, fcli cannot determine when the token will expire.
logging in with a pre-generated CIToken, this token will be stored as-is after checking its validity. \
Note that on SSC 23.2 or below, fcli cannot determine when a pre-generated token will expire.
fcli.sc-dast.session.login.usage.description.2 = %nFor interactive use, you may choose to keep the session \
open until it expires. For CI/CD integrations and other automations, you should always issue a \
logout command once work is complete. On shared/non-containerized systems, consider setting \
Expand All @@ -72,16 +72,26 @@ fcli.sc-dast.session.login.ssc-url.1 = Environment variables:%n \

# fcli sc-dast session logout
fcli.sc-dast.session.logout.usage.header = Terminate ScanCentral DAST session.
fcli.sc-dast.session.logout.usage.description = This command terminates a ScanCentral DAST session \
previously created through the 'login' command. This command will try to revoke the token passed \
to or generated by the 'login' command (unless the --no-revoke-token option is specified), and \
removes the session data from the fcli state data directory.
fcli.sc-dast.session.logout.no-revoke-token = Don't revoke the token passed to or generated by the 'login' \
command. If user name and password were specified during login, it is recommended to have the 'logout' \
command revoke this token, to avoid exceeding SSCs maximum token limit. Note that due to SSC limitations, \
user credentials will need to be provided to allow for token revocation. If a pre-generated token was \
specified during login, usually you'll want to pass this option to avoid the pre-generated token from \
being revoked.
fcli.sc-dast.session.logout.usage.description.0 = This command terminates a ScanCentral DAST session previously created \
through the 'login' command.
fcli.sc-dast.session.logout.usage.description.1 = %nIf the session was created with user credentials, this command \
will revoke the automatically generated SSC token unless the --no-revoke-token option is specified. \
On SSC 24.2 or above, the automatically generated token can be revoked without providing user \
credentials. For SSC 23.2 or below, user credentials are required to revoke the automatically \
generated token; the logout command will throw an error if neither user credentials nor --no-revoke-token \
option is specified, and the session will not be terminated.
fcli.sc-dast.session.logout.usage.description.2 = %nIf the session was created with a pre-generated token, the \
session will always be terminated without revoking the pre-generated token. As such, no user credentials need \
to be provided, and the --no-revoke-token option will have no effect.
fcli.sc-dast.session.logout.usage.description.3 = %nTo summarize: No user credentials nor --no-revoke-token option \
should be specified if the session was created with a pre-generated SSC token or if the session is connected to \
SSC 24.2 or above. Either user credentials or --no-revoke-token option must be specified if the session was \
created with user credentials AND the session is connected to SSC 23.2 or below.
fcli.sc-dast.session.logout.no-revoke-token = It is highly recommended to have fcli revoke the token that was \
automatically generated if the session was created using user credentials to avoid exceeding SSCs maximum \
token limit. This option is provided for convenience only, to allow the session to be terminated without \
having to specify user credentials on SSC 23.2 or below. Once most users have upgraded to SSC 24.2 or above, \
this option will be deprecated or removed.

# fcli sc-dast session list
fcli.sc-dast.session.list.usage.header = List active and expired ScanCentral DAST sessions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
package com.fortify.cli.sc_sast._common.session.cli.cmd;

import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
import com.fortify.cli.common.rest.unirest.UnexpectedHttpResponseException;
import com.fortify.cli.common.session.cli.cmd.AbstractSessionLogoutCommand;
import com.fortify.cli.common.session.helper.SessionLogoutException;
import com.fortify.cli.sc_sast._common.session.cli.mixin.SCSastSessionLogoutOptions;
import com.fortify.cli.sc_sast._common.session.cli.mixin.SCSastUserCredentialOptions;
import com.fortify.cli.sc_sast._common.session.helper.SCSastSessionDescriptor;
import com.fortify.cli.sc_sast._common.session.helper.SCSastSessionHelper;

Expand All @@ -29,7 +32,18 @@ public class SCSastSessionLogoutCommand extends AbstractSessionLogoutCommand<SCS
@Mixin private SCSastSessionLogoutOptions logoutOptions;

@Override
protected void logout(String sessionName, SCSastSessionDescriptor sessionData) {
sessionData.logout(logoutOptions.getUserCredentialOptions());
protected void logout(String sessionName, SCSastSessionDescriptor sessionDescriptor) {
if ( !logoutOptions.isNoRevokeToken() ) {
SCSastUserCredentialOptions userCredentialOptions = logoutOptions.getUserCredentialOptions();
try {
sessionDescriptor.logout(userCredentialOptions);
} catch ( UnexpectedHttpResponseException e ) {
if ( e.getStatus()==403 && userCredentialOptions==null ) {
throw new SessionLogoutException("SSC user credentials or --no-revoke-token option must be specified on SSC versions 23.2 or below", false);
} else {
throw e;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ public static class SSCCredentialOptions implements ISSCCredentialsConfig {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private SCSastUserCredentialAndExpiryOptions userCredentialsConfig = new SCSastUserCredentialAndExpiryOptions();
@ArgGroup(exclusive = false, multiplicity = "1", order = 2)
@Getter private SCDastTokenCredentialOptions tokenOptions = new SCDastTokenCredentialOptions();
@Getter private SCSastTokenCredentialOptions tokenOptions = new SCSastTokenCredentialOptions();

@Override
public char[] getPredefinedToken() {
return tokenOptions==null || tokenOptions.token==null ? null : SSCTokenConverter.toRestToken(tokenOptions.token);
}
}

public static class SCDastTokenCredentialOptions {
public static class SCSastTokenCredentialOptions {
// Note that the SCSastControllerScanStartCommand requires this predefined token to be
// a CIToken. If we ever add support for passing arbitrary tokens (i.e. through a new
// --ssc-token option), we should be sure that we can distinguish between token passed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,8 @@
import picocli.CommandLine.Option;

public class SCSastSessionLogoutOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private SSCAuthOptions authOptions = new SSCAuthOptions();

public static class SSCAuthOptions {
@ArgGroup(exclusive = true, multiplicity = "1", order = 2)
@Getter private SSCCredentialOptions credentialOptions = new SSCCredentialOptions();
}

public static class SSCCredentialOptions {
@ArgGroup(exclusive = false, multiplicity = "1", order = 1)
@Getter private SCSastUserCredentialOptions userOptions = new SCSastUserCredentialOptions();
@Option(names={"--no-revoke-token"})
@Getter private boolean noRevokeToken;
}

public SCSastUserCredentialOptions getUserCredentialOptions() {
return authOptions.credentialOptions.noRevokeToken ? null : authOptions.credentialOptions.userOptions;
}
@ArgGroup(exclusive = false, multiplicity = "0..1")
@Getter private SCSastUserCredentialOptions userCredentialOptions;
@Option(names={"--no-revoke-token"})
@Getter private boolean noRevokeToken = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ public SCSastSessionDescriptor(IUrlConfig sscUrlConfig, IUrlConfig scSastUrlConf

@JsonIgnore
public void logout(IUserCredentialsConfig userCredentialsConfig) {
if ( cachedSscTokenResponse!=null && userCredentialsConfig!=null ) {
SSCTokenHelper.deleteTokensById(getSscUrlConfig(), userCredentialsConfig, getTokenId());
// We only revoke the token if we generated a token upon login,
// and that token hasn't expired yet.
if ( predefinedSscToken==null && hasActiveCachedTokenResponse() ) {
SSCTokenHelper.revokeToken(getSscUrlConfig(), userCredentialsConfig, cachedSscTokenResponse.getData().getToken());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ fcli.sc-sast.session.login.usage.description.0 = This command stores the SSC and
CI/CD pipeline use) as listed below.
fcli.sc-sast.session.login.usage.description.1 = %nWhen logging in with username and password, this \
command will connect to SSC to generate a CIToken; the user password is not stored by fcli. When \
logging in with a pre-generated CIToken, this token will be stored as-is. Note that due to SSC \
limitations, fcli cannot determine when the token will expire.
logging in with a pre-generated CIToken, this token will be stored as-is after checking its validity. \
Note that on SSC 23.2 or below, fcli cannot determine when a pre-generated token will expire.
fcli.sc-sast.session.login.usage.description.2 = %nFor interactive use, you may choose to keep the session \
open until it expires. For CI/CD integrations and other automations, you should always issue a \
logout command once work is complete. On shared/non-containerized systems, consider setting \
Expand All @@ -77,16 +77,26 @@ fcli.sc-sast.session.login.ssc-url.1 = Environment variables:%n \

# fcli sc-sast session logout
fcli.sc-sast.session.logout.usage.header = Terminate ScanCentral SAST session.
fcli.sc-sast.session.logout.usage.description = This command terminates a ScanCentral SAST session \
previously created through the 'login' command. This command will try to revoke the token passed \
to or generated by the 'login' command (unless the --no-revoke-token option is specified), and \
removes the session data from the fcli state data directory.
fcli.sc-sast.session.logout.no-revoke-token = Don't revoke the token passed to or generated by the 'login' \
command. If user name and password were specified during login, it is recommended to have the 'logout' \
command revoke this token, to avoid exceeding SSCs maximum token limit. Note that due to SSC limitations, \
user credentials will need to be provided to allow for token revocation. If a pre-generated token was \
specified during login, usually you'll want to pass this option to avoid the pre-generated token from \
being revoked.
fcli.sc-sast.session.logout.usage.description.0 = This command terminates a ScanCentral SAST session previously created \
through the 'login' command.
fcli.sc-sast.session.logout.usage.description.1 = %nIf the session was created with user credentials, this command \
will revoke the automatically generated SSC token unless the --no-revoke-token option is specified. \
On SSC 24.2 or above, the automatically generated token can be revoked without providing user \
credentials. For SSC 23.2 or below, user credentials are required to revoke the automatically \
generated token; the logout command will throw an error if neither user credentials nor --no-revoke-token \
option is specified, and the session will not be terminated.
fcli.sc-sast.session.logout.usage.description.2 = %nIf the session was created with a pre-generated token, the \
session will always be terminated without revoking the pre-generated token. As such, no user credentials need \
to be provided, and the --no-revoke-token option will have no effect.
fcli.sc-sast.session.logout.usage.description.3 = %nTo summarize: No user credentials nor --no-revoke-token option \
should be specified if the session was created with a pre-generated SSC token or if the session is connected to \
SSC 24.2 or above. Either user credentials or --no-revoke-token option must be specified if the session was \
created with user credentials AND the session is connected to SSC 23.2 or below.
fcli.sc-sast.session.logout.no-revoke-token = It is highly recommended to have fcli revoke the token that was \
automatically generated if the session was created using user credentials to avoid exceeding SSCs maximum \
token limit. This option is provided for convenience only, to allow the session to be terminated without \
having to specify user credentials on SSC 23.2 or below. Once most users have upgraded to SSC 24.2 or above, \
this option will be deprecated or removed.

# fcli sc-sast session list
fcli.sc-sast.session.list.usage.header = List active and expired ScanCentral SAST sessions.
Expand Down
Loading

0 comments on commit cb7867b

Please sign in to comment.