Implement IPsec Cert-Renewal Operation

This commit adds IPsec Cert-Renewal implementation to work
properly when specified by "--opcode" parameter in IPsec
client execution.

This implementation adds to IPsec client a rekey step after
the generated keys and cert are stored and exchanged during
cert-renewal operation. The main goal of this implementation
is to provide new certificates and keys for an IPsec client
host that has already been authenticated by IPsec server host.

Test Plan:
PASS: Full build, system install, bootstrap and unlock DX system w/
      unlocked enabled available status.
PASS: Execute "ipsec-client pxecontroller --opcode 2" in controller-1.
      Observe the previously created CertificateRequest was deleted and
      generated a new one for controller-1's node. The new certificate
      is sent to IPsec Client and stored with the swanctl rekey command
      executed sucessfully.
PASS: In a DC system with available enabled active status with IPsec
      server being executed from controller-0. Change c0 and c1 dates
      to expire IPsec certificates. If needed, recover kubernetes
      certificates or pods. Execute "sudo ipsec-client pxecontroller
      -o 2" command from controller-0 and controller-1. Observe that
      certificates and keys were generated and stored in /etc/swanctl/
      directory. Observe new SAs have been created between controllers
      by executing "sudo swanctl --list-sas" command.

Story: 2010940
Task: 49656

Change-Id: I69383005c2e204fe0a6401b2efaf05e8754f2bc3
Signed-off-by: Manoel Benedito Neto <Manoel.BeneditoNeto@windriver.com>
This commit is contained in:
Manoel Benedito Neto 2024-02-28 16:14:17 -03:00
parent eb27f7bbf3
commit 937ce744b0
6 changed files with 64 additions and 34 deletions

View File

@ -46,7 +46,7 @@ def main():
help="If enabled, the logging level will be set "
"to DEBUG instead of the default INFO level.")
parser.add_argument("-o", "--opcode", metavar='<opcode>',
type=int, choices=[1, 2, 3],
type=int, choices=[1, 2],
help='Operational code (Default: ' + str(opcode) + ')')
args = parser.parse_args()

View File

@ -29,10 +29,10 @@ LOG = logging.getLogger(__name__)
class Client(object):
def __init__(self, host, port, opcode):
def __init__(self, host, port, op_code):
self.host = host
self.port = port
self.opcode = opcode
self.op_code = str(op_code)
self.state = State.STAGE_1
self.ifname = utils.get_management_interface()
self.personality = utils.get_personality()
@ -45,7 +45,7 @@ class Client(object):
# Generate message 1 - OP/MAC/HASH
def _generate_message_1(self):
message = {}
message['op'] = str(self.opcode)
message['op'] = self.op_code
message['mac_addr'] = self.mac_addr
message['hash'] = utils.hash_payload(message)
@ -159,12 +159,15 @@ class Client(object):
if self.state == State.STAGE_4:
LOG.info("Received IPSec Auth CSR Response")
cert = base64.b64decode(msg['cert'])
network = msg['network']
digest = base64.b64decode(msg['hash'])
ca_cert = utils.load_data(constants.TRUSTED_CA_CERT_PATH)
data = msg['cert'].encode('utf-8') + network.encode('utf-8')
data = msg['cert'].encode('utf-8')
if self.op_code == constants.OP_CODE_INITIAL_AUTH:
network = msg['network']
data = data + network.encode('utf-8')
if not utils.verify_signed_hash(ca_cert, digest, data):
msg = "Hash validation failed"
LOG.exception("Hash validation failed")
@ -176,23 +179,46 @@ class Client(object):
cert_path = constants.CERT_SYSTEM_LOCAL_DIR + cert_file
utils.save_data(cert_path, cert)
if self.personality == constants.CONTROLLER:
self.local_addr = self.hostname[constants.UNIT_HOSTNAME] + ', ' \
+ self.hostname[constants.FLOATING_UNIT_HOSTNAME]
else:
self.local_addr = utils.get_ip_addr(self.ifname)
if self.op_code == constants.OP_CODE_INITIAL_AUTH:
if self.personality == constants.CONTROLLER:
self.local_addr = self.hostname[constants.UNIT_HOSTNAME] + ', ' \
+ self.hostname[constants.FLOATING_UNIT_HOSTNAME]
else:
self.local_addr = utils.get_ip_addr(self.ifname)
LOG.info("Generating config files and restart ipsec")
strong = config.StrongswanPuppet(self.hostname[constants.UNIT_HOSTNAME],
self.local_addr, network)
strong.generate_file()
puppet_cf = subprocess.run(['puppet', 'apply', '-e', 'include ::platform::strongswan'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
LOG.info("Generating config files and restart ipsec")
strong = config.StrongswanPuppet(self.hostname[constants.UNIT_HOSTNAME],
self.local_addr, network)
strong.generate_file()
puppet_cf = subprocess.run(['puppet', 'apply', '-e',
'include ::platform::strongswan'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=False)
if puppet_cf.stderr:
err = "Error: %s" % (puppet_cf.stderr.decode("utf-8"))
LOG.exception("Failed to create strongswan config files: %s" % err)
return False
if puppet_cf.returncode != 0:
err = "Error: %s" % (puppet_cf.stderr.decode("utf-8"))
LOG.exception("Failed to create StrongSwan config files: %s" % err)
return False
elif self.op_code == constants.OP_CODE_CERT_RENEWAL:
load_creds = subprocess.run(['swanctl', '--load-creds'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False)
if load_creds.returncode != 0:
err = "Error: %s" % (load_creds.stderr.decode("utf-8"))
LOG.exception("Failed to load StrongSwan credentials: %s" % err)
return False
rekey = subprocess.run(['swanctl', '--rekey', '--ike', constants.IKE_SA_NAME,
'--reauth'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False)
if rekey.returncode != 0:
err = "Error: %s" % (rekey.stderr.decode("utf-8"))
LOG.exception("Failed to rekey IKE SA with StrongSwan: %s" % err)
return False
LOG.info('IPsec certificate renewed successfully')
return True

View File

@ -90,11 +90,11 @@ class SwanctlConf(object):
self.node[key] = value
def get_conf(self):
self.children['node'] = self.node
self.children[constants.CHILD_SA_NAME] = self.node
self.system_nodes['local'] = self.local
self.system_nodes['remote'] = self.remote
self.system_nodes['children'] = self.children
self.connections['system-nodes'] = self.system_nodes
self.connections[constants.IKE_SA_NAME] = self.system_nodes
return self.connections

View File

@ -49,10 +49,12 @@ PXECONTROLLER_URL = 'http://pxecontroller:6385'
OP_CODE_INITIAL_AUTH = "1"
OP_CODE_CERT_RENEWAL = "2"
SUPPORTED_OP_CODES = [OP_CODE_INITIAL_AUTH,
OP_CODE_CERT_RENEWAL]
MGMT_IPSEC_ENABLING = 'enabling'
MGMT_IPSEC_ENABLED = 'enabled'
MGMT_IPSEC_DISABLED = 'disabled'
CHILD_SA_NAME = 'node'
IKE_SA_NAME = 'system-nodes'

View File

@ -282,7 +282,7 @@ def kube_apply_certificate_request(body):
elif get_cr.stderr and 'NotFound' not in str(get_cr.stderr):
err = "Error: %s" % (get_cr.stderr.decode("utf-8"))
LOG.exception("Failed to retrieve CertificateRequest resource info. %s" % (err))
return
return None
# Create CertificateRequest resource in kubernetes
cr_body = yaml.safe_dump(body, default_flow_style=False)
@ -292,11 +292,11 @@ def kube_apply_certificate_request(body):
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=False)
if create_cr.stderr:
if create_cr.returncode != 0:
err = "Error: %s" % (create_cr.stderr.decode("utf-8"))
LOG.exception("Failed to create CertificateRequest %s/%s. %s"
% (constants.NAMESPACE_DEPLOYMENT, name, err))
return
return None
# Get Certificate from recently created resource in kubernetes
cmd_get_certificate = ['-o', "jsonpath='{.status.certificate}'"]
@ -305,10 +305,10 @@ def kube_apply_certificate_request(body):
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=False)
if signed_cert.stderr:
if signed_cert.returncode != 0:
err = "Error: %s" % (signed_cert.stderr.decode("utf-8"))
LOG.exception("Failed to retrieve %s/%s's Certificate. %s"
% (constants.NAMESPACE_DEPLOYMENT, name, err))
return
return None
return signed_cert.stdout.decode("utf-8").strip("'")

View File

@ -90,6 +90,7 @@ class IPsecConnection(object):
CA_CRT = 'tls.crt'
def __init__(self):
self.op_code = None
self.hostname = None
self.mgmt_subnet = None
self.signed_cert = None
@ -153,7 +154,6 @@ class IPsecConnection(object):
raise ConnectionRefusedError(msg)
client_data = utils.get_client_hostname_and_mgmt_subnet(mac_addr)
self.hostname = client_data['hostname']
self.mgmt_subnet = client_data['mgmt_subnet']
@ -199,12 +199,14 @@ class IPsecConnection(object):
if not self.signed_cert:
raise ValueError('Unable to sign certificate request')
cert = bytes(self.signed_cert, 'utf-8')
network = bytes(self.mgmt_subnet, 'utf-8')
hash_payload = utils.hash_and_sign_payload(self.ca_key, cert + network)
data = bytes(self.signed_cert, 'utf-8')
if self.op_code == constants.OP_CODE_INITIAL_AUTH:
payload["network"] = self.mgmt_subnet
data = data + bytes(self.mgmt_subnet, 'utf-8')
hash_payload = utils.hash_and_sign_payload(self.ca_key, data)
payload["cert"] = self.signed_cert
payload["network"] = self.mgmt_subnet
payload["hash"] = hash_payload.decode("utf-8")
LOG.info("Sending IPSec Auth CSR Response")