def test_sort_action_groups(self): """Test sort_action_groups sorts action groups by custom title order. """ # Create custom order for titles (default is asserted in setUp) custom_order = ["optional arguments", "positional arguments"] sort_action_groups(self.parser, title_order=custom_order) # Assert successful re-ordering self.assertListEqual( [group.title for group in self.parser._action_groups], custom_order) # Add custom group to parser that exists in most in-toto command line tools self.parser.add_argument_group("required named arguments") # Test default custom order of action groups titles (which are title-cased) title_case_action_groups(self.parser) sort_action_groups(self.parser) default_custom_order = [ "Required Named Arguments", "Positional Arguments", "Optional Arguments" ] # Assert successful(title-casing) re-ordering self.assertListEqual( [group.title for group in self.parser._action_groups], default_custom_order)
def create_parser(): """Parse arguments and call in_toto_mock. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" in-toto-mock is a variant of 'in-toto-run' that can be used to create unsigned link metadata, using defaults for many of the 'in-toto-run' arguments. in-toto-mock verbosely executes the passed command, records all files in the current working directory as materials and products, and generates a link file under '<name>.link'. This is useful for trying out how to generate a link without the need for a key, or knowledge about all 'in-toto-run' arguments. It can also be used to quickly generate link metadata, inspect it and sign it retroactively. """) parser.usage = "%(prog)s [-h] --name <name> -- <command> [args]" parser.epilog = """EXAMPLE USAGE Generate unsigned link metadata 'foo.link' for the activity of creating file 'bar', inspect it, and sign it with 'mykey' # Generate unsigned link {prog} --name foo -- touch bar # Inspect and/or update unsigned link metadata vi foo.link # Sign the link, attesting to its validity, and write it to # 'foo.<mykey keyid prefix>.link'. in-toto-sign -k mykey -f foo.link """.format(prog=parser.prog) named_args = parser.add_argument_group("required named arguments") # FIXME: Do we limit the allowed characters for the name? named_args.add_argument( "-n", "--name", type=str, required=True, metavar="<name>", help=( "name for the resulting link metadata file, which is written to" " '<name>.link'. It is also used to associate the link with a step" " defined in an in-toto layout.")) # FIXME: This is not yet ideal. # What should we do with tokens like > or ; ? parser.add_argument( "link_cmd", nargs="+", metavar="<command>", help=("command to be executed. It is separated from named and optional" " arguments by a double dash '--'.")) parser.add_argument('--version', action='version', version='{} {}'.format(parser.prog, __version__)) title_case_action_groups(parser) sort_action_groups(parser) return parser
def create_parser(): """Create and return configured ArgumentParser instance. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" in-toto-verify is the main verification tool of the suite, and it is used to verify that the software supply chain of the delivered product was carried out as defined in the passed in-toto supply chain layout. Evidence for supply chain steps must be available in the form of link metadata files named '<step name>.<functionary keyid prefix>.link'. Both 'in-toto-run' and 'in-toto-record' generate link metadata named in this manner. If you require special handling of the in-toto link metadata files, please take a look at the library api to modify this behavior. The verification includes the following checks: * the layout is signed with the passed keys, * the layout has not expired, * a threshold of link metadata files exists for each step of the layout, * link files are signed by the authorized functionaries, * the materials and products for each step, as reported by the corresponding link files, adhere to the artifact rules specified by the step. Additionally, inspection commands defined in the layout are executed sequentially, followed by processing the inspections' artifact rules. If the layout includes sublayouts, the verification routine will recurse into a subdirectory named '<step name>.<keyid prefix>', where all the links relevant to that sublayout must exist. The sublayout itself must be in the same directory as the other links of the superlayout. (i.e. '<step name>.<keyid prefix>.link') The command returns 2 if it is called with wrong arguments, 1 if in-toto verification fails and 0 if verification passes. """) parser.usage = "%(prog)s <named arguments> [optional arguments]" parser.epilog = """EXAMPLE USAGE Verify supply chain in 'root.layout', signed with private part of 'key_file.pub'. {prog} --layout root.layout --layout-keys key_file.pub Verify supply chain as above but load links corresponding to steps of 'root.layout' from 'link_dir'. {prog} --layout root.layout --layout-keys key_file.pub \\ --link-dir link_dir Verify supply chain in 'root.layout', signed with GPG key '...7E0C8A17', for which the public part can be found in the GPG keyring at '~/.gnupg'. {prog} --layout root.layout \\ --gpg 8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17 \\ --gpg-home ~/.gnupg """.format(prog=parser.prog) named_args = parser.add_argument_group("required named arguments") named_args.add_argument( "-l", "--layout", type=str, required=True, metavar="<path>", help=("path to root layout specifying the software supply chain to be" " verified.")) named_args.add_argument( "-k", "--layout-keys", type=str, metavar="<path>", nargs="+", help= ("paths to public key files used to verify the passed root layout's" " signatures. See '--key-types' for available formats. Passing at least" " one key using '--layout-keys' and/or '--gpg' is required. For each" " passed key the layout must carry a valid signature.")) parser.add_argument( "-t", "--key-types", dest="key_types", type=str, choices=in_toto.util.SUPPORTED_KEY_TYPES, nargs="+", help= ("types of keys specified by the '--layout-keys' option. '{rsa}' keys are" " expected in a 'PEM' format and '{ed25519}' in a custom" " 'securesystemslib/json' format. If multiple keys are passed via" " '--layout-keys' the same amount of key types must be passed. Key" " types are then associated with keys by index. If '--key-types' is" " omitted, the default of '{rsa}' is used for all keys.".format( rsa=in_toto.util.KEY_TYPE_RSA, ed25519=in_toto.util.KEY_TYPE_ED25519))) named_args.add_argument( "-g", "--gpg", nargs="+", metavar="<id>", help= ("GPG keyid, identifying a public key in the GPG keyring used to verify" " the passed root layout's signatures." " Passing at least one key using '--layout-keys' and/or '--gpg' is" " required. For each passed key the layout must carry a valid" " signature.")) parser.add_argument( "--link-dir", dest="link_dir", type=str, metavar="<path>", default=".", help=( "path to directory from which link metadata files for steps defined" " in the root layout should be loaded. If not passed, links are" " loaded from the current working directory.")) parser.add_argument(*GPG_HOME_ARGS, **GPG_HOME_KWARGS) verbosity_args = parser.add_mutually_exclusive_group(required=False) verbosity_args.add_argument(*VERBOSE_ARGS, **VERBOSE_KWARGS) verbosity_args.add_argument(*QUIET_ARGS, **QUIET_KWARGS) parser.add_argument('--version', action='version', version='{} {}'.format(parser.prog, __version__)) title_case_action_groups(parser) sort_action_groups(parser) return parser
def create_parser(): """Create and return configured ArgumentParser instance. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" in-toto-record creates a signed link metadata file in two steps, in order to provide evidence for supply chain steps that cannot be carried out by a single command (for which 'in-toto-run' should be used). It returns a non-zero value on failure and zero otherwise.""") parser.epilog = """EXAMPLE USAGE Create link metadata file in two commands, signing it with the private key loaded from 'key_file', recording all files in the CWD as materials (on start), and as products (on stop). {prog} start -n edit-files -k path/to/key_file -m . {prog} stop -n edit-files -k path/to/key_file -p . Create link metadata file signed with the default GPG key from the default GPG home directory and record a file named 'foo' as material and product. {prog} start -n edit-foo --gpg -m path/to/foo {prog} stop -n edit-foo --gpg -p path/to/foo Create link metadata file signed with the private key loaded from 'key_file', record all files in the CWD as material and product, and dump finished link file to the target directory (on stop). {prog} start -n edit-files -k path/to/key_file -m . {prog} stop -d path/to/target/dir -n edit-files -k path/to/key_file -p . """.format(prog=parser.prog) # The subparsers inherit the arguments from the parent parser parent_parser = argparse.ArgumentParser(add_help=False) subparsers = parser.add_subparsers(dest="command") # Workaround to make subcommands mandatory in Python>=3.3 # https://bugs.python.org/issue9253#msg186387 subparsers.required = True parent_named_args = parent_parser.add_argument_group( "required named arguments") # FIXME: Do we limit the allowed characters for the name? parent_named_args.add_argument( "-n", "--step-name", type=str, required=True, metavar="<name>", help= ("name for the resulting link metadata file. It is also used to associate" " the link with a step defined in an in-toto layout.")) parent_named_args.add_argument(*KEY_ARGS, **KEY_KWARGS) parent_parser.add_argument(*KEY_TYPE_ARGS, **KEY_TYPE_KWARGS) parent_parser.add_argument(*KEY_PASSWORD_ARGS, **KEY_PASSWORD_KWARGS) parent_named_args.add_argument(*GPG_ARGS, **GPG_KWARGS) parent_parser.add_argument(*GPG_HOME_ARGS, **GPG_HOME_KWARGS) parent_parser.add_argument(*EXCLUDE_ARGS, **EXCLUDE_KWARGS) parent_parser.add_argument(*BASE_PATH_ARGS, **BASE_PATH_KWARGS) parent_parser.add_argument(*LSTRIP_PATHS_ARGS, **LSTRIP_PATHS_KWARGS) verbosity_args = parent_parser.add_mutually_exclusive_group(required=False) verbosity_args.add_argument(*VERBOSE_ARGS, **VERBOSE_KWARGS) verbosity_args.add_argument(*QUIET_ARGS, **QUIET_KWARGS) subparser_start = subparsers.add_parser( "start", parents=[parent_parser], help=( "creates a preliminary link file recording the paths and hashes of" " the passed materials and signs it with the passed functionary's" " key. The resulting link file is stored as" " '.<name>.<keyid prefix>.link-unfinished'.")) subparser_stop = subparsers.add_parser( "stop", parents=[parent_parser], help= ("expects preliminary link file '.<name>.<keyid prefix>.link-unfinished'" " in the CWD, signed by the passed functionary's key. If found, it" " records and adds the paths and hashes of the passed products to the" " link metadata file, updates the signature and renames the file to" " '<name>.<keyid prefix>.link'.")) subparser_start.add_argument( "-m", "--materials", type=str, required=False, nargs='+', metavar="<path>", help= ("paths to files or directories, whose paths and hashes are stored in the" " resulting link metadata's material section when running the 'start'" " subcommand. Symlinks to files are followed.")) subparser_stop.add_argument( "-p", "--products", type=str, required=False, nargs='+', metavar="<path>", help= ("paths to files or directories, whose paths and hashes are stored in the" " resulting link metadata's product section when running the 'stop'" " subcommand. Symlinks to files are followed.")) subparser_stop.add_argument(*METADATA_DIRECTORY_ARGS, **METADATA_DIRECTORY_KWARGS) parser.add_argument('--version', action='version', version='{} {}'.format(parser.prog, __version__)) for _parser, _order in [(parser, ["Positional Arguments", "Optional Arguments"]), (subparser_start, None), (subparser_stop, None)]: title_case_action_groups(_parser) sort_action_groups(_parser, _order) return parser
def create_parser(): """Create and return configured ArgumentParser instance. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" in-toto-sign provides a command line interface to sign in-toto link or layout metadata or verify their signatures, with options to: * replace (default) or add signatures: + layout metadata can be signed by multiple keys at once, + link metadata can only be signed by one key at a time. * write signed metadata to a specified path. If no output path is specified, + layout metadata is written to the path of the input file, + link metadata is written to '<name>.<keyid prefix>.link'. * verify signatures in-toto-sign is useful to re-sign metadata (e.g. when changing keys), or to sign unsigned links (e.g. generated with 'in-toto-mock'). For layouts, it is useful to append signatures in case threshold signing of layouts is necessary. It returns a non-zero value on failure and zero otherwise.""") parser.epilog = """EXAMPLE USAGE Sign 'unsigned.layout' with two keys and write it to 'root.layout'. {prog} -f unsigned.layout -k priv_key1 priv_key2 -o root.layout Replace signature in link file and write to default filename, i.e. 'package.<priv_key keyid prefix>.link'. {prog} -f package.2f89b927.link -k priv_key Verify layout signed with 3 keys. {prog} -f root.layout -k pub_key0 pub_key1 pub_key2 --verify Sign layout with default gpg key in default gpg keyring. {prog} -f root.layout --gpg Verify layout with a gpg key identified by keyid '...439F3C2'. {prog} -f root.layout --verify \\ --gpg 3BF8135765A07E21BD12BF89A5627F6BF439F3C2 """.format(prog=parser.prog) named_args = parser.add_argument_group("required named arguments") named_args.add_argument( "-f", "--file", type=str, required=True, metavar="<path>", help=("path to link or layout file to be signed or verified.")) parser.add_argument( "-k", "--key", nargs="+", metavar="<path>", help= ("paths to key files, used to sign the passed link or layout metadata" " or to verify its signatures. See '--key-type' for available formats." )) parser.add_argument( "-t", "--key-type", dest="key_type", type=str, choices=util.SUPPORTED_KEY_TYPES, nargs="+", help=( "types of keys specified by the '--key' option. '{rsa}' keys are" " expected in a 'PEM' format and '{ed25519}' in a custom" " 'securesystemslib/json' format. If multiple keys are passed via" " '--key' the same amount of key types must be passed. Key" " types are then associated with keys by index. If '--key-type' is" " omitted, the default of '{rsa}' is used for all keys.".format( rsa=util.KEY_TYPE_RSA, ed25519=util.KEY_TYPE_ED25519))) parser.add_argument( "-g", "--gpg", nargs="*", metavar="<id>", help= ("GPG keyids used to sign the passed link or layout metadata or to verify" " its signatures. If passed without keyids, the default GPG key is" " used.")) parser.add_argument(*GPG_HOME_ARGS, **GPG_HOME_KWARGS) # Only when signing parser.add_argument( "-o", "--output", type=str, metavar="<path>", help= ("path to location where the metadata file is stored after signing. If" " not passed, layout metadata is written to the path of the input file" " and link metadata is written to '<name>.<keyid prefix>.link'")) # Only when signing parser.add_argument( "-a", "--append", action="store_true", help= ("add signatures rather than replacing existing signatures. This option" " is only availabe for layout metdata.")) parser.add_argument( "--verify", action="store_true", help="verify signatures of passed link or layout metadata using the" " public keys passed via '--key' and/or '--gpg' options.") verbosity_args = parser.add_mutually_exclusive_group(required=False) verbosity_args.add_argument(*VERBOSE_ARGS, **VERBOSE_KWARGS) verbosity_args.add_argument(*QUIET_ARGS, **QUIET_KWARGS) parser.add_argument('--version', action='version', version='{} {}'.format(parser.prog, __version__)) title_case_action_groups(parser) sort_action_groups(parser) return parser
def create_parser(): """Create and return configured ArgumentParser instance. """ parser = argparse.ArgumentParser( formatter_class=argparse.RawDescriptionHelpFormatter, description=""" in-toto-run is the main command line interface for generating link metadata while carrying out a supply chain step. To do this, it wraps the passed command, and attempts to track all relevant information about the wrapped command's execution. It records paths and hashes of 'materials' (files before command execution) and 'products' (files after command execution) and writes them together with other information (executed command, return value, stdout and stderr) to a link metadata file, which is signed with the passed key. It returns a non-zero value on failure and zero otherwise.""") parser.usage = ("%(prog)s <named arguments> [optional arguments] \\\n\t" " -- <command> [args]") parser.epilog = """EXAMPLE USAGE Tag a git repo, storing files in CWD as products, signing the resulting link file with the private key loaded from 'key_file'. {prog} -n tag -p . -k key_file -- git tag v1.0 Create a tarball, storing files in 'project' directory as materials and the tarball as product, signing the link file with a GPG key '...7E0C8A17'. {prog} -n package -m project -p project.tar.gz \\ -g 8465A1E2E0FB2B40ADB2478E18FB3F537E0C8A17 \\ -- tar czf project.tar.gz project Not all supply chain steps require that a command be executed. in-toto can still generate signed attestations, e.g. for review work. In that case, files may be marked as materials for the manual review process and the command be omitted. {prog} -n review -k key_file -m document.pdf -x If an artifact that should be recorded is not in the current working directory (or one of its subdirectories) it can be located using the base path option. Note that in this example only the relative path, 'document.pdf' is stored along with its hash in the resulting link metadata file. {prog} -n review -k key_file -m document.pdf \\ --base-path /my/review/docs/ -x Similarly, it is possible to pass the full path to the artifact that should be recorded together with a left-strip path, to only store a relative path, e.g. 'document.pdf'. {prog} -n review -k key_file \\ -m /tmp/my/review/docs/document.pdf \\ --lstrip-paths /tmp/my/review/docs/ -x """.format(prog=parser.prog) named_args = parser.add_argument_group("required named arguments") # FIXME: Do we limit the allowed characters for the name? named_args.add_argument("-n", "--step-name", type=str, required=True, metavar="<name>", help=( "name for the resulting link metadata file, which is written to" " '<name>.<keyid prefix>.link'. It is also used to associate the link" " with a step defined in an in-toto layout.")) parser.add_argument("-m", "--materials", type=str, required=False, nargs='+', metavar="<path>", help=( "paths to files or directories, for which paths and hashes are stored in" " the resulting link metadata before the command is executed. Symlinks" " to files are followed.")) parser.add_argument("-p", "--products", type=str, required=False, nargs='+', metavar="<path>", help=( "paths to files or directories, for which paths and hashes are stored in" " the resulting link metadata after the command is executed. Symlinks to" " files are followed.")) parser.add_argument("-s", "--record-streams", dest="record_streams", default=False, action="store_true", help=( "duplicate 'stdout' and 'stderr' of the executed command and store the" " contents in the resulting link metadata. Do not use with interactive" " commands.")) parser.add_argument("-x", "--no-command", dest="no_command", default=False, action="store_true", help=( "generate link metadata without executing a command, e.g. for a" " signed-off-by step.")) named_args.add_argument(*KEY_ARGS, **KEY_KWARGS) parser.add_argument(*KEY_TYPE_ARGS, **KEY_TYPE_KWARGS) named_args.add_argument(*GPG_ARGS, **GPG_KWARGS) parser.add_argument(*GPG_HOME_ARGS, **GPG_HOME_KWARGS) parser.add_argument(*EXCLUDE_ARGS, **EXCLUDE_KWARGS) parser.add_argument(*BASE_PATH_ARGS, **BASE_PATH_KWARGS) parser.add_argument(*LSTRIP_PATHS_ARGS, **LSTRIP_PATHS_KWARGS) verbosity_args = parser.add_mutually_exclusive_group(required=False) verbosity_args.add_argument(*VERBOSE_ARGS, **VERBOSE_KWARGS) verbosity_args.add_argument(*QUIET_ARGS, **QUIET_KWARGS) # FIXME: This is not yet ideal. # What should we do with tokens like > or ; ? parser.add_argument("link_cmd", nargs="*", metavar="<command>", help=( "command to be executed. It is separated from named and optional" " arguments by a double dash '--'.")) parser.add_argument('--version', action='version', version='{} {}'.format(parser.prog, __version__)) title_case_action_groups(parser) sort_action_groups(parser) return parser