Skip to content

Commit a3441ae

Browse files
committed
Code changes for supporting Offline LAS licensing.
Signed-off-by: lakshmj <[email protected]>
1 parent f2e00c5 commit a3441ae

3 files changed

Lines changed: 75 additions & 33 deletions

File tree

examples/nslaslicense_offline.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
nitro_pass: "{{ nitro_pass }}"
1212
nitro_protocol: "{{ nitro_protocol | default('https') }}"
1313
validate_certs: false
14-
request_pem: CNS_V10000_SERVER
15-
request_ed: Premium
14+
entitlement_name: "VPX 10000 Premium"
1615
is_fips: false
1716
las_secrets_json: /path/to/las_secrets.json
1817
register: lic_result

plugins/module_utils/las_utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,21 @@ def get_fingerprint_for_lsguid(self, bearer, loglines):
266266
ls_list = self._post_json(url, headers, {"ver": "1.0"})
267267
for ls in ls_list.get("lstlasactivatedls", []):
268268
if ls["lsguid"] == self.lsguid:
269-
break
269+
return ls.get("lsfingerprint", "") or ""
270270
return ""
271271
except Exception as e:
272272
loglines.append("ERROR: get_fingerprint_for_lsguid: {0}".format(str(e)))
273273
return "EXCEPTION ERROR"
274274

275+
def get_customer_entitlements(self, bearer, platform, loglines):
276+
url = "{0}/{1}/netscalerfixedbw/customerentitlements".format(self._base_url, self._ccid)
277+
headers = {"Content-Type": "application/json", "Authorization": "CWSAuth bearer={0}".format(bearer)}
278+
try:
279+
return self._post_json(url, headers, {"ver": "1.0", "platform": platform})
280+
except Exception as e:
281+
loglines.append("ERROR: get_customer_entitlements platform={0}: {1}".format(platform, str(e)))
282+
return None
283+
275284
def import_offline_activation_request(self, request_file, fingerprint, bearer, loglines):
276285
url = "{0}/support/{1}/{2}/importofflineactivationrequest".format(self._base_url, self._ccid, self.endpoint)
277286
base_data = json.dumps({"ver": "1.0", "lsfingerprint": fingerprint})

plugins/modules/nslaslicense_offline.py

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,12 @@
6262
- If C(false), SSL certificates will not be validated.
6363
type: bool
6464
default: true
65-
request_pem:
65+
entitlement_name:
6666
description:
67-
- The PEM entitlement identifier for the device (e.g. C(CNS_8905_SERVER)).
67+
- The full entitlement name for the device license (e.g. C(MPX 8905 Premium)).
68+
- Combines the device model and edition, with an optional C(FIPS) prefix for FIPS appliances.
6869
type: str
6970
required: true
70-
request_ed:
71-
description:
72-
- The license edition.
73-
type: str
74-
required: true
75-
choices:
76-
- Advanced
77-
- Premium
78-
- Standard
7971
is_fips:
8072
description:
8173
- Set to C(true) for FIPS-enabled appliances.
@@ -99,8 +91,7 @@
9991
nitro_pass: "{{ nitro_pass }}"
10092
nitro_protocol: https
10193
validate_certs: false
102-
request_pem: CNS_8905_SERVER
103-
request_ed: Premium
94+
entitlement_name: MPX 8905 Premium
10495
is_fips: false
10596
las_secrets_json: /etc/netscaler/zmcd_secrets.json
10697
@@ -110,19 +101,17 @@
110101
nsip: 10.102.201.231
111102
nitro_user: nsroot
112103
nitro_pass: "{{ nitro_pass }}"
113-
request_pem: CNS_V5000_SERVER
114-
request_ed: Premium
104+
entitlement_name: FIPS VPX 5000 Premium
115105
is_fips: true
116106
las_secrets_json: /etc/netscaler/zmcd_secrets.json
117107
118-
- name: Retrieve activation request package only (for debugging)
108+
- name: Generate and apply offline LAS license for NS (MPX) Standard edition
119109
delegate_to: localhost
120110
netscaler.adc.nslaslicense_offline:
121111
nsip: 10.102.201.230
122112
nitro_user: nsroot
123113
nitro_pass: "{{ nitro_pass }}"
124-
request_pem: CNS_8905_SERVER
125-
request_ed: Standard
114+
entitlement_name: MPX 8905 Standard
126115
las_secrets_json: /etc/netscaler/zmcd_secrets.json
127116
"""
128117

@@ -161,7 +150,7 @@
161150

162151
from ..module_utils.las_utils import (
163152
HAS_PARAMIKO,
164-
MPX14K_PEMS,
153+
LASClient,
165154
NEW_API_MAPPING_FIPS,
166155
NEW_API_MAPPING_NS,
167156
NitroHelper,
@@ -170,7 +159,6 @@
170159
check_ns_version,
171160
extract_lsguid,
172161
generate_offline_package,
173-
get_ent_name,
174162
get_offline_request_package,
175163
)
176164

@@ -187,8 +175,7 @@ def main():
187175
nitro_pass=dict(required=True, type="str", no_log=True, fallback=(env_fallback, ["NETSCALER_NITRO_PASS"])),
188176
nitro_protocol=dict(type="str", choices=["http", "https"], default="https"),
189177
validate_certs=dict(type="bool", default=True, fallback=(env_fallback, ["NETSCALER_VALIDATE_CERTS"])),
190-
request_pem=dict(required=True, type="str"),
191-
request_ed=dict(required=True, type="str", choices=["Advanced", "Premium", "Standard"]),
178+
entitlement_name=dict(required=True, type="str"),
192179
is_fips=dict(type="bool", default=False),
193180
las_secrets_json=dict(required=True, type="str", no_log=False),
194181
)
@@ -207,25 +194,72 @@ def main():
207194
ip = module.params["nsip"]
208195
username = module.params["nitro_user"]
209196
password = module.params["nitro_pass"]
210-
request_pem = module.params["request_pem"]
211-
request_ed = module.params["request_ed"]
197+
ent_name = module.params["entitlement_name"]
212198
is_fips = module.params["is_fips"]
213199
las_secrets_json = module.params["las_secrets_json"]
214200
if username != "nsroot":
215201
module.fail_json(msg="Only the 'nsroot' account is supported. Got: '{0}'".format(username), **result)
216202

217-
if is_fips and request_pem in MPX14K_PEMS:
218-
module.fail_json(msg="MPX 14K devices (CNS_14xxx) do not require the is_fips argument", **result)
219-
220203
if not os.path.isfile(las_secrets_json):
221204
module.fail_json(msg="las_secrets_json not found: {0}".format(las_secrets_json), **result)
222205

223-
ent_name = get_ent_name(request_pem, request_ed, is_fips, loglines)
224-
if not ent_name:
206+
_valid_ent_prefixes = (
207+
"FIPS MPX 14",
208+
"FIPS MPS 15",
209+
"FIPS MPX 16",
210+
"FIPS MPS 89",
211+
"FIPS MPX 91",
212+
"FIPS MPX 92",
213+
"MPS 14",
214+
"MPX 15",
215+
"MPX 16",
216+
"MPX 17",
217+
"MPS 25",
218+
"MPX 26",
219+
"MPX 59",
220+
"MPX 89",
221+
"MPX 91",
222+
"MPX 92",
223+
"VPX",
224+
)
225+
if not ent_name.startswith(_valid_ent_prefixes):
225226
module.fail_json(
226-
msg="Could not resolve entitlement name for pem={0}, ed={1}, fips={2}".format(request_pem, request_ed, is_fips),
227+
msg="Invalid entitlement_name '{0}'. Must start with one of: {1}".format(ent_name, ", ".join(_valid_ent_prefixes)),
228+
**result,
229+
)
230+
231+
# Derive LAS platform from the matched prefix (spaces → underscores)
232+
matched_prefix = next(p for p in _valid_ent_prefixes if ent_name.startswith(p))
233+
platform = matched_prefix.replace(" ", "_")
234+
235+
# Validate entitlement_name against customer entitlements from LAS
236+
las_client = LASClient("", las_secrets_json)
237+
bearer = las_client.validate_bearer_cache()
238+
if not bearer:
239+
bearer = las_client.generate_bearer_token()
240+
loglines.append("INFO: New bearer token generated for entitlement validation")
241+
else:
242+
loglines.append("INFO: Using cached bearer token for entitlement validation")
243+
if not bearer:
244+
module.fail_json(msg="Failed to obtain bearer token from LAS to validate entitlement_name", **result)
245+
246+
ent_resp = las_client.get_customer_entitlements(bearer, platform, loglines)
247+
if ent_resp is None:
248+
module.fail_json(
249+
msg="Failed to fetch customer entitlements from LAS for platform '{0}'".format(platform),
250+
**result,
251+
)
252+
253+
valid_entitlements = [e.get("type", "") for e in ent_resp.get("entitlements", [])]
254+
loglines.append("INFO: Valid entitlements for platform '{0}': {1}".format(platform, valid_entitlements))
255+
if ent_name not in valid_entitlements:
256+
module.fail_json(
257+
msg="entitlement_name '{0}' is not a valid customer entitlement for platform '{1}'. Valid entitlements: [{2}]".format(
258+
ent_name, platform, ", ".join(valid_entitlements) if valid_entitlements else "none found"
259+
),
227260
**result,
228261
)
262+
loglines.append("INFO: entitlement_name '{0}' validated successfully against LAS".format(ent_name))
229263

230264
nitro = NitroHelper(ip, module.params["nitro_protocol"], username, password, module.params["validate_certs"], loglines)
231265

0 commit comments

Comments
 (0)