diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..71c5f90 --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ +registry=https://registry.npmjs.org/ +fetch-retry-mintimeout=1000 +fetch-retry-maxtimeout=5000 +strict-ssl=false \ No newline at end of file diff --git a/README.md b/README.md index 3c365c2..c3abb05 100755 --- a/README.md +++ b/README.md @@ -129,6 +129,8 @@ Commands: disable-override-http-header-logging (Beta) Disable the HTTP Header Logging Override settings. prefetch-requests (Beta) Display the Prefetch Requests settings. modify-prefetch-requests (Beta) Update the Prefetch Requests settings. + pragma-header (Beta) Display Pragma Header settings. + modify-pragma-header (Beta) Update Pragma Header settings. create-config (Beta) Create a new security config. delete-config (Beta) Delete a security config. create-security-policy (Beta) Create a security policy. @@ -210,4 +212,4 @@ The Akamai CLI is a new tool and as such we have made some design choices worth * Credentials - the tool looks for credentials in the 'appsec' section in your ~/.edgerc file. If not present, it will look for the section 'default'. Alternatively you can provide the section name using the --section option in every command. If you are unfamiliar with the authentication and provisioning for OPEN APIs, see the "Get Started" section of https://developer.akamai.com ## References -1A configuration version is editable if it is not active currently or in the past in any of the environments(staging or production). \ No newline at end of file +1A configuration version is editable if it is not active currently or in the past in any of the environments(staging or production). diff --git a/bin/commands/apimatchtarget.modify.js b/bin/commands/apimatchtarget.modify.js index f57fd2e..5d78e09 100644 --- a/bin/commands/apimatchtarget.modify.js +++ b/bin/commands/apimatchtarget.modify.js @@ -2,7 +2,7 @@ let out = require('./lib/out'); let MatchTarget = require('../../src/matchtarget').matchTarget; let logger = require('../../src/constants').logger('modify-api-match-target'); -const SUB_CPMMANDS = ['add-api']; +const SUB_CPMMANDS = ['add-api', 'remove-api']; class ModifyAPIMatchTargetCommand { constructor() { this.flags = 'modify-api-match-target'; @@ -55,6 +55,8 @@ class ModifyAPIMatchTargetCommand { switch (options.subcommand) { case 'add-api': return new MatchTarget(options).addApi(); + case 'remove-api': + return new MatchTarget(options).removeApi(); default: return null; } diff --git a/bin/commands/matchtarget.modify.js b/bin/commands/matchtarget.modify.js index a680894..2fbbd75 100644 --- a/bin/commands/matchtarget.modify.js +++ b/bin/commands/matchtarget.modify.js @@ -2,7 +2,7 @@ let out = require('./lib/out'); let MatchTarget = require('../../src/matchtarget').matchTarget; let logger = require('../../src/constants').logger('modify-match-target'); -const SUB_CPMMANDS = ['add-hostname']; +const SUB_CPMMANDS = ['add-hostname', 'remove-hostname']; class ModifyMatchTargetCommand { constructor() { this.flags = 'modify-match-target'; @@ -42,7 +42,9 @@ class ModifyMatchTargetCommand { run(options) { logger.debug(JSON.stringify(options)); - options.hostnames = [options.hostname]; + if (options.hostname) { + options.hostnames = [options.hostname]; + } out.print({ promise: this._getOperation(options), args: options, @@ -56,6 +58,8 @@ class ModifyMatchTargetCommand { switch (options.subcommand) { case 'add-hostname': return new MatchTarget(options).addHostnames(); + case 'remove-hostname': + return new MatchTarget(options).removeHostname(); default: return null; } diff --git a/bin/commands/pragmaheader.js b/bin/commands/pragmaheader.js new file mode 100644 index 0000000..e7d510e --- /dev/null +++ b/bin/commands/pragmaheader.js @@ -0,0 +1,43 @@ +let AdvancedSettings = require('../../src/advancedsettings').advancedsettings; +let out = require('./lib/out'); + +class PragmaHeaderCommand { + constructor() { + this.flags = 'pragma-header'; + this.desc = '(Beta) Display Pragma Header settings'; + this.setup = this.setup.bind(this); + this.run = this.run.bind(this); + } + + setup(sywac) { + sywac + .number('--config ', { + desc: 'Configuration ID. Mandatory if you have more than one configuration.', + group: 'Optional:', + required: false + }) + .string('--version ', { + desc: + "Version Number. It can also take the values 'PROD' or 'PRODUCTION' or 'STAGING'. If not provided, latest version is assumed.", + group: 'Optional:', + required: false + }) + .string('--policy ', { + desc: + 'Policy ID. If not provided, we try to use the policy available on file. If you have more than one policy, this option must be provided.', + group: 'Optional:', + required: false + }); + } + run(options) { + out.print({ + promise: new AdvancedSettings(options).getPragmaHeader(), + args: options, + success: (args, data) => { + return JSON.stringify(data); + } + }); + } +} + +module.exports = new PragmaHeaderCommand (); diff --git a/bin/commands/pragmaheader.modify.js b/bin/commands/pragmaheader.modify.js new file mode 100644 index 0000000..e2cacda --- /dev/null +++ b/bin/commands/pragmaheader.modify.js @@ -0,0 +1,46 @@ +let AdvancedSettings = require('../../src/advancedsettings').advancedsettings; +let out = require('./lib/out'); + +class PragmaHeaderModifyCommand { + constructor() { + this.flags = 'modify-pragma-header'; + this.desc = '(Beta) Update Pragma Header settings.'; + this.setup = this.setup.bind(this); + this.run = this.run.bind(this); + } + + setup(sywac) { + sywac + .positional('<@path>', { + paramsDesc: 'The input file path.' + }) + .number('--config ', { + desc: 'Configuration ID. Mandatory if you have more than one configuration.', + group: 'Optional:', + required: false + }) + .string('--version ', { + desc: + "Version Number. It can also take the values 'PROD' or 'PRODUCTION' or 'STAGING'. If not provided, latest version is assumed.", + group: 'Optional:', + required: false + }) + .check((argv, context) => { + if (!argv['@path'].startsWith('@')) { + return context.cliMessage("ERROR: Invalid file name, should start with '@'"); + } + }); + } + run(options) { + options.file = options['@path'].replace('@', ''); + out.print({ + promise: new AdvancedSettings(options).updatePragmaHeader(), + args: options, + success: (args, data) => { + return JSON.stringify(data); + } + }); + } +} + +module.exports = new PragmaHeaderModifyCommand(); diff --git a/cli.json b/cli.json index 4e8baae..f64c132 100644 --- a/cli.json +++ b/cli.json @@ -5,7 +5,7 @@ "commands": [ { "name": "appsec", - "version": "2.0.0", + "version": "2.1.0", "description": "Akamai Security tools for protecting websites." } ] diff --git a/package-lock.json b/package-lock.json index f197869..d683764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "akamaicliappsec", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 664a10c..57366c8 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "akamaicliappsec", - "version": "2.0.0", + "version": "2.1.0", "description": "A wrapping development kit to interface common tasks with akamai's Security {OPEN} API.", "repository": "https://github.com/akamai/cli-appsec", "license": "Apache-2.0", diff --git a/src/advancedsettings.js b/src/advancedsettings.js index a6cdd2b..08b303a 100644 --- a/src/advancedsettings.js +++ b/src/advancedsettings.js @@ -104,6 +104,36 @@ class AdvancedSettings { throw `The file does not exists: ${this._options['file']}`; } } + + getPragmaHeader() { + if (this._options.policy) { + return this._policyProvider.policyId().then(policyId => { + return this._version.readResource(URIs.SECURITY_POLICY_PRAGMA_HEADER, [policyId]); + }); + } + return this._version.readResource(URIs.PRAGMA_HEADER, []); + } + + updatePragmaHeader() { + if (fs.existsSync(this._options['file'])) { + let payload = fs.readFileSync(untildify(this._options['file']), 'utf8'); + let data; + try { + data = JSON.parse(payload); + } catch (err) { + throw 'The input JSON is not valid'; + } + if (this._options.policy) { + return this._policyProvider.policyId().then(policyId => { + return this._version.updateResource(URIs.SECURITY_POLICY_PRAGMA_HEADER, [policyId], data); + }); + } + return this._version.updateResource(URIs.PRAGMA_HEADER, [], data); + } else { + throw `The file does not exists: ${this._options['file']}`; + } + } + } module.exports = { diff --git a/src/constants.js b/src/constants.js index f2ee427..765a4b8 100644 --- a/src/constants.js +++ b/src/constants.js @@ -81,6 +81,8 @@ const resources = { BYPASS_NETWORK_LIST: '/appsec/v1/configs/%s/versions/%s/bypass-network-lists', HTTP_HEADER_LOGGING: '/appsec/v1/configs/%s/versions/%s/advanced-settings/logging', PREFETCH: '/appsec/v1/configs/%s/versions/%s/advanced-settings/prefetch', + PRAGMA_HEADER: '/appsec/v1/configs/%s/versions/%s/advanced-settings/pragma-header', + SECURITY_POLICY_PRAGMA_HEADER:'/appsec/v1/configs/%s/versions/%s/security-policies/%s/advanced-settings/pragma-header', SECURITY_POLICY_HTTP_HEADER_LOGGING: '/appsec/v1/configs/%s/versions/%s/security-policies/%s/advanced-settings/logging', VERSION_NOTES: '/appsec/v1/configs/%s/versions/%s/version-notes', diff --git a/src/matchtarget.js b/src/matchtarget.js index 2165877..a9c39b2 100644 --- a/src/matchtarget.js +++ b/src/matchtarget.js @@ -91,6 +91,33 @@ class MatchTarget { }); } + removeHostname() { + return this._version + .readResource(URIs.MATCH_TARGET, [this._options['match-target']]) + .then(matchTarget => { + matchTarget.hostnames = matchTarget.hostnames || []; + let hostExists = false; + for (let i = 0; i < matchTarget.hostnames.length; i++) { + if (matchTarget.hostnames[i] === this._options.hostname) { + matchTarget.hostnames.splice(i, 1); + hostExists = true; + break; + } + } + if (!hostExists) { + throw 'The specified hostname not present in the match target'; + } + + delete matchTarget.validations; + logger.debug('Updated match target: %s', JSON.stringify(matchTarget)); + return this._version.updateResource( + URIs.MATCH_TARGET, + [this._options['match-target']], + matchTarget + ); + }); + } + addApi() { return this._version .readResource(URIs.MATCH_TARGET, [this._options['match-target']]) @@ -102,6 +129,7 @@ class MatchTarget { for (let i = 0; i < matchTarget.apis.length; i++) { if (matchTarget.apis[i].id == this._options.api) { apiExists = true; + break; } } if (!apiExists) { @@ -117,6 +145,32 @@ class MatchTarget { }); } + removeApi() { + return this._version + .readResource(URIs.MATCH_TARGET, [this._options['match-target']]) + .then(matchTarget => { + matchTarget.apis = matchTarget.apis || []; + let apiExists = false; + for (let i = 0; i < matchTarget.apis.length; i++) { + if (matchTarget.apis[i].id == this._options.api) { + matchTarget.apis.splice(i, 1); + apiExists = true; + break; + } + } + if (!apiExists) { + throw 'The specified api not present in the match target'; + } + delete matchTarget.validations; + logger.debug('Updated match target: %s', JSON.stringify(matchTarget)); + return this._version.updateResource( + URIs.MATCH_TARGET, + [this._options['match-target']], + matchTarget + ); + }); + } + _updateOrder(targetIdsInOrder) { let targetSequence = []; for (let i = 0; i < targetIdsInOrder.length; i++) { diff --git a/templates/pragma-header.json b/templates/pragma-header.json new file mode 100644 index 0000000..b4292f5 --- /dev/null +++ b/templates/pragma-header.json @@ -0,0 +1,19 @@ +{ + "action": "REMOVE", + "excludeCondition": [ + { + "type":"requestHeaderValueMatch", + "positiveMatch": true, + "header": "accept", + "value": [ "application/json", "application/xml"], + "valueCase": false, + "valueWildcard": true + }, + { + "type":"ipMatch", + "positiveMatch": true, + "value": [ "1.1.1.1", "192.168.100.14/24"], + "useHeaders": false + } + ] +}