Secret Strings

Secret strings (such as passwords) may be specified in an encoded form suitable for sharing with a wide audience, while ensuring that only authorized users are able to decode these strings into their plaintext form.

A secret string class is provided that behaves like a string but has some special features.

Refer to Configuration for more details on secret string related configuration.

See pyats secret for details on the CLI commands related to secret string manipulation.

Warning

By default, pyATS secret strings are not cryptographically secure.

How to secure your secret strings

Follow this procedure to make your secret strings cryptographically secure:

  1. Update your pyATS configuration file as follows:

    [secrets]
    string.representer = pyats.utils.secret_strings.FernetSecretStringRepresenter
    
  2. Install the cryptography package:

    > pip install cryptography
    
  3. Ensure the permissions are restricted on your pyATS configuration file to prevent others from reading it. For example:

    > chmod 600 ~/.pyats/pyats.conf
    
  4. Generate a cryptographic key:

    > pyats secret keygen
    Newly generated key :
    dSvoKX23jKQADn20INt3W3B5ogUQmh6Pq00czddHtgU=
    
  5. Update your pyATS configuration file as follows:

    [secrets]
    string.representer = pyats.utils.secret_strings.FernetSecretStringRepresenter
    string.key = dSvoKX23jKQADn20INt3W3B5ogUQmh6Pq00czddHtgU=
    
  6. Encode a password:

    (RECOMMENDED)
    > pyats secret encode
    Password: MySecretPassword
    Encoded string :
    gAAAAABdsgvwElU9_3RTZsRnd4b1l3Es2gV6Y_DUnUE8C9y3SdZGBc2v0B2m9sKVz80jyeYhlWKMDwtqfwlbg4sQ2Y0a843luOrZyyOuCgZ7bxE5X3Dk_NY=
    
    OR
    
    > pyats secret encode --string MySecretPassword
    Encoded string :
    gAAAAABdsgvwElU9_3RTZsRnd4b1l3Es2gV6Y_DUnUE8C9y3SdZGBc2v0B2m9sKVz80jyeYhlWKMDwtqfwlbg4sQ2Y0a843luOrZyyOuCgZ7bxE5X3Dk_NY=
    
  7. Do a test decode of the encoded password:

    > pyats secret decode gAAAAABdsgvwElU9_3RTZsRnd4b1l3Es2gV6Y_DUnUE8C9y3SdZGBc2v0B2m9sKVz80jyeYhlWKMDwtqfwlbg4sQ2Y0a843luOrZyyOuCgZ7bxE5X3Dk_NY=
    Decoded string :
    MySecretPassword
    
  8. Add your encoded password to a testbed.yaml %ENC{} block, as described in Credential Password Modeling. Now your password is secured. The only way to decode the password from the testbed YAML file is to use the same pyATS configuration file used to encode the password:

    # Snippet of your testbed.yaml
    testbed:
        name: sampleTestbed
        credentials:
            default:
                username: admin
                password: "%ENC{gAAAAABdsgvwElU9_3RTZsRnd4b1l3Es2gV6Y_DUnUE8C9y3SdZGBc2v0B2m9sKVz80jyeYhlWKMDwtqfwlbg4sQ2Y0a843luOrZyyOuCgZ7bxE5X3Dk_NY=}"
    

Multiple Representers

Follow this procedure to specify multiple representers if there are several kinds of encoded strings you want to specify in a file such as a testbed YAML:

  1. Add your new representer to the pyATS configuration file as follows:

    [secrets]
    my_custom.representer = package.module.MyRepresenterClass
    
  2. Generate a key if your representer requires it:

    > pyats secret keygen --prefix my_custom
    Newly generated key :
    <generated key for my_custom>
    
  3. Update your pyATS configuration file with the newly generated key (if required) as follows:

    [secrets]
    my_custom.representer = package.module.MyRepresenterClass
    my_custom.key = <generated key for my_custom>
    
  4. Encode a password using the default representer:

    > pyats secret encode
    Password: MySecretPassword
    Encoded string :
    wr3DssK0w5nDlsORw4nDmcK2w4LDqMOfw6vDjsOdw4k=
    
  5. Encode a password using the my_custom representer:

    > pyats secret encode --prefix my_custom
    Password: MySecretPassword
    Encoded string :
    <my_custom encoded string>
    
  6. Add references to your encoded passwords to your testbed YAML file, for example:

    testbed:
        credentials:
            default:
                username: my_username
                password: "%ENC{wr3DssK0w5nDlsORw4nDmcK2w4LDqMOfw6vDjsOdw4k=}"
            alternate:
                username: alternate_username
                password: "%ENC{<my_custom encoded string>, prefix=my_custom}"
        custom:
            custom_key: |4-
                custom data containing encoded text
                %ENC{<my_custom encoded string>, prefix=my_custom}
    
  7. Check that your passwords can be recovered from the loaded testbed:

    > pyats shell --testbed_file my_testbed.yaml
    >>> from pyats.utils.secret_strings import to_plaintext
    >>> to_plaintext(testbed.credentials.default.password)
    'MySecretPassword'
    >>> to_plaintext(testbed.credentials.alternate.password)
    'MySecretPassword'
    >>> testbed.custom.custom_key
    'custom data containing encoded text\nMySecretPassword'
    

Secret String Object

+--------------------------------------------------------------------------+
| SecretString object                                                      |
+==========================================================================+
| class methods  | description                                             |
|----------------+---------------------------------------------------------|
| from_plaintext | returns an encoded secret string from plaintext         |
|----------------+---------------------------------------------------------|
| keygen         | returns a key that affects the string encoding/decoding |
+==========================================================================+
| properties     | description                                             |
|----------------+---------------------------------------------------------|
| plaintext      | returns the decoded secret string in plaintext form     |
+==========================================================================+
| attributes     | description                                             |
|----------------+---------------------------------------------------------|
| data           | the secret string in encoded form                       |
+==========================================================================+
| methods        | description                                             |
|----------------+---------------------------------------------------------|
| __str__        | returns asterisks in order to hide the secret string    |
+--------------------------------------------------------------------------+

Example

# Example
# -------
#
#   creating secret string objects

from pyats.utils.secret_strings import to_plaintext, SecretString

# Create a secret string by specifying its encoded form.
# The ``pyats secret encode`` CLI command may be used to convert a
# plaintext string to encoded form.
my_secret = SecretString('w53DssKBw6fDmMOCw5bDisOa')

# Decode the password
my_secret.plaintext
'my password'

# Create a secret string from plaintext.
my_secret =  SecretString.from_plaintext('another secret')

# Asterisks are shown when the secret is printed.
print(my_secret)
**************

# Print the secret string in plaintext form.
# The ``pyats secret decode`` CLI command may be used to convert
# an encoded string to plaintext form.
to_plaintext(my_secret)
'another secret'

# to_plaintext works on regular strings as well.
to_plaintext('plain string')
'plain string'

# Print the secret string in its encoded form.
my_secret.data
'w53DssKBw6fDmMOCw5bDisOa'

# Allocate a brand new key.
# This does the same thing as the ``pyats secret keygen`` CLI command.
print(SecretString.keygen())

# Allocate a secret string that stores a string in plaintext (non-encoded)
# form.
from pyats.utils.secret_strings import PlainTextSecretStringRepresenter
weak_secret = SecretString('weak_pw', representer_cls= PlainTextSecretStringRepresenter)

Representer Classes

The encoding/decoding of secret strings and any required key management is defined in a pluggable manner by the use of representer classes.

Supported Representers

The following representers are supported:

  • pyats.utils.secret_strings.ObscuringSecretStringRepresenter - This class stores the secret string in cipher-encoded form.

    • It is the default representer if the user has not specified a representer in pyATS configuration.

    • It uses a default key, but allows the user to overwrite the key in pyATS configuration.

  • pyats.utils.secret_strings.PlainTextSecretStringRepresenter - This class stores the secret string in plaintext form.

    • It does not make use of the key.

  • pyats.utils.secret_strings.FernetSecretStringRepresenter

    • This class stores the secret string in crypto-encoded form.

    • It requires the user to manually execute pip install cryptography.

    • It can generate a decryption key.

    • A generated key must be specified in pyATS configuration.

Sample Representer Implementation

from pyats.utils.secret_strings import BaseSecretStringRepresenter

class MySecretStringRepresenter(BaseSecretStringRepresenter):
    """ My secret string representer """
    @classmethod
    def keygen(cls):
        return my_generate_key()

    @property
    def key(self):
        key = super().key
        if key == self.DEFAULT_KEY:
            raise Exception("A key must be specified as pyATS "
                "configuration under [secrets] string.key.\n"
                "This key may be generated with "
                "the 'pyats secret keygen' command")
        return key

    def encode(self):
        """ Encode a contained plaintext string object """
        return my_encode(key=self.key, data=self.obj.data)

    def decode(self):
        """ Decode a contained encoded string object """
        return my_decode(key=self.key, data=self.obj.data)