1# -*- coding: utf-8 -*- 2# Copyright: (c) 2020, Adam Migus <adam@migus.org> 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4from __future__ import absolute_import, division, print_function 5 6__metaclass__ = type 7 8DOCUMENTATION = r""" 9name: tss 10author: Adam Migus (@amigus) <adam@migus.org> 11short_description: Get secrets from Thycotic Secret Server 12version_added: 1.0.0 13description: 14 - Uses the Thycotic Secret Server Python SDK to get Secrets from Secret 15 Server using token authentication with I(username) and I(password) on 16 the REST API at I(base_url). 17requirements: 18 - python-tss-sdk - https://pypi.org/project/python-tss-sdk/ 19options: 20 _terms: 21 description: The integer ID of the secret. 22 required: true 23 type: int 24 base_url: 25 description: The base URL of the server, e.g. C(https://localhost/SecretServer). 26 env: 27 - name: TSS_BASE_URL 28 ini: 29 - section: tss_lookup 30 key: base_url 31 required: true 32 username: 33 description: The username with which to request the OAuth2 Access Grant. 34 env: 35 - name: TSS_USERNAME 36 ini: 37 - section: tss_lookup 38 key: username 39 password: 40 description: 41 - The password associated with the supplied username. 42 - Required when I(token) is not provided. 43 env: 44 - name: TSS_PASSWORD 45 ini: 46 - section: tss_lookup 47 key: password 48 domain: 49 default: "" 50 description: 51 - The domain with which to request the OAuth2 Access Grant. 52 - Optional when I(token) is not provided. 53 - Requires C(python-tss-sdk) version 1.0.0 or greater. 54 env: 55 - name: TSS_DOMAIN 56 ini: 57 - section: tss_lookup 58 key: domain 59 required: false 60 version_added: 3.6.0 61 token: 62 description: 63 - Existing token for Thycotic authorizer. 64 - If provided, I(username) and I(password) are not needed. 65 - Requires C(python-tss-sdk) version 1.0.0 or greater. 66 env: 67 - name: TSS_TOKEN 68 ini: 69 - section: tss_lookup 70 key: token 71 version_added: 3.7.0 72 api_path_uri: 73 default: /api/v1 74 description: The path to append to the base URL to form a valid REST 75 API request. 76 env: 77 - name: TSS_API_PATH_URI 78 required: false 79 token_path_uri: 80 default: /oauth2/token 81 description: The path to append to the base URL to form a valid OAuth2 82 Access Grant request. 83 env: 84 - name: TSS_TOKEN_PATH_URI 85 required: false 86""" 87 88RETURN = r""" 89_list: 90 description: 91 - The JSON responses to C(GET /secrets/{id}). 92 - See U(https://updates.thycotic.net/secretserver/restapiguide/TokenAuth/#operation--secrets--id--get). 93 type: list 94 elements: dict 95""" 96 97EXAMPLES = r""" 98- hosts: localhost 99 vars: 100 secret: >- 101 {{ 102 lookup( 103 'community.general.tss', 104 102, 105 base_url='https://secretserver.domain.com/SecretServer/', 106 username='user.name', 107 password='password' 108 ) 109 }} 110 tasks: 111 - ansible.builtin.debug: 112 msg: > 113 the password is {{ 114 (secret['items'] 115 | items2dict(key_name='slug', 116 value_name='itemValue'))['password'] 117 }} 118 119- hosts: localhost 120 vars: 121 secret: >- 122 {{ 123 lookup( 124 'community.general.tss', 125 102, 126 base_url='https://secretserver.domain.com/SecretServer/', 127 username='user.name', 128 password='password', 129 domain='domain' 130 ) 131 }} 132 tasks: 133 - ansible.builtin.debug: 134 msg: > 135 the password is {{ 136 (secret['items'] 137 | items2dict(key_name='slug', 138 value_name='itemValue'))['password'] 139 }} 140 141- hosts: localhost 142 vars: 143 secret_password: >- 144 {{ 145 ((lookup( 146 'community.general.tss', 147 102, 148 base_url='https://secretserver.domain.com/SecretServer/', 149 token='thycotic_access_token', 150 ) | from_json).get('items') | items2dict(key_name='slug', value_name='itemValue'))['password'] 151 }} 152 tasks: 153 - ansible.builtin.debug: 154 msg: the password is {{ secret_password }} 155""" 156 157import abc 158 159from ansible.errors import AnsibleError, AnsibleOptionsError 160from ansible.module_utils import six 161from ansible.plugins.lookup import LookupBase 162from ansible.utils.display import Display 163 164try: 165 from thycotic.secrets.server import SecretServer, SecretServerError 166 167 HAS_TSS_SDK = True 168except ImportError: 169 SecretServer = None 170 SecretServerError = None 171 HAS_TSS_SDK = False 172 173try: 174 from thycotic.secrets.server import PasswordGrantAuthorizer, DomainPasswordGrantAuthorizer, AccessTokenAuthorizer 175 176 HAS_TSS_AUTHORIZER = True 177except ImportError: 178 PasswordGrantAuthorizer = None 179 DomainPasswordGrantAuthorizer = None 180 AccessTokenAuthorizer = None 181 HAS_TSS_AUTHORIZER = False 182 183 184display = Display() 185 186 187@six.add_metaclass(abc.ABCMeta) 188class TSSClient(object): 189 def __init__(self): 190 self._client = None 191 192 @staticmethod 193 def from_params(**server_parameters): 194 if HAS_TSS_AUTHORIZER: 195 return TSSClientV1(**server_parameters) 196 else: 197 return TSSClientV0(**server_parameters) 198 199 def get_secret(self, term): 200 display.debug("tss_lookup term: %s" % term) 201 202 secret_id = self._term_to_secret_id(term) 203 display.vvv(u"Secret Server lookup of Secret with ID %d" % secret_id) 204 205 return self._client.get_secret_json(secret_id) 206 207 @staticmethod 208 def _term_to_secret_id(term): 209 try: 210 return int(term) 211 except ValueError: 212 raise AnsibleOptionsError("Secret ID must be an integer") 213 214 215class TSSClientV0(TSSClient): 216 def __init__(self, **server_parameters): 217 super(TSSClientV0, self).__init__() 218 219 if server_parameters.get("domain"): 220 raise AnsibleError("The 'domain' option requires 'python-tss-sdk' version 1.0.0 or greater") 221 222 self._client = SecretServer( 223 server_parameters["base_url"], 224 server_parameters["username"], 225 server_parameters["password"], 226 server_parameters["api_path_uri"], 227 server_parameters["token_path_uri"], 228 ) 229 230 231class TSSClientV1(TSSClient): 232 def __init__(self, **server_parameters): 233 super(TSSClientV1, self).__init__() 234 235 authorizer = self._get_authorizer(**server_parameters) 236 self._client = SecretServer( 237 server_parameters["base_url"], authorizer, server_parameters["api_path_uri"] 238 ) 239 240 @staticmethod 241 def _get_authorizer(**server_parameters): 242 if server_parameters.get("token"): 243 return AccessTokenAuthorizer( 244 server_parameters["token"], 245 ) 246 247 if server_parameters.get("domain"): 248 return DomainPasswordGrantAuthorizer( 249 server_parameters["base_url"], 250 server_parameters["username"], 251 server_parameters["domain"], 252 server_parameters["password"], 253 server_parameters["token_path_uri"], 254 ) 255 256 return PasswordGrantAuthorizer( 257 server_parameters["base_url"], 258 server_parameters["username"], 259 server_parameters["password"], 260 server_parameters["token_path_uri"], 261 ) 262 263 264class LookupModule(LookupBase): 265 def run(self, terms, variables, **kwargs): 266 if not HAS_TSS_SDK: 267 raise AnsibleError("python-tss-sdk must be installed to use this plugin") 268 269 self.set_options(var_options=variables, direct=kwargs) 270 271 tss = TSSClient.from_params( 272 base_url=self.get_option("base_url"), 273 username=self.get_option("username"), 274 password=self.get_option("password"), 275 domain=self.get_option("domain"), 276 token=self.get_option("token"), 277 api_path_uri=self.get_option("api_path_uri"), 278 token_path_uri=self.get_option("token_path_uri"), 279 ) 280 281 try: 282 return [tss.get_secret(term) for term in terms] 283 except SecretServerError as error: 284 raise AnsibleError("Secret Server lookup failure: %s" % error.message) 285