We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Genrated certificate in install bundle for Execution node is valid only for 10 days
### Please confirm the following

- [X] I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).
- [X] I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.
- [X] I understand that AWX is open source software provided for free and that I might not receive a timely response.

### Bug Summary

The certificate for receptor in install bundle (`*.tar.gz`) for execution node that generated by AWX is valid only for 10 days.

In my understanding currently there is no automated certificate renewal feature in both AWX and Receptor, so I guess the execution environment will be invalidated after 10 days. 

Therefore frequently renewal (generating bundle and invoke playbook again) by hand might be required.

```bash
$ ls -l
total 24
-rw-rw-r--. 1 kuro kuro 11278 Oct  7 05:12 ansible.log
drwxrwxr-x. 2 kuro kuro    21 Oct  7 05:09 group_vars
-rw-r--r--. 1 kuro kuro   406 Jan  1  1970 install_receptor.yml
-rw-r--r--. 1 kuro kuro   159 Oct  7 05:10 inventory.yml
drwxrwxr-x. 3 kuro kuro    44 Oct  7 05:09 receptor
-rw-r--r--. 1 kuro kuro   137 Jan  1  1970 requirements.yml

$ openssl x509 -text -in receptor/tls/receptor.crt -noout | grep Not
            Not Before: Oct  6 20:09:21 2022 GMT
            Not After : Oct 16 20:09:21 2022 GMT     👈👈👈
```

Installer (playbook) just place certificate on execution node without any modification, so deployed execution node uses certificate that included in the install bandle as is. 

I have no idea if this is a _bug_ or _designed behavior_, but just in case I'm creating this issue.

### AWX version

21.7.0

### Select the relevant components

- [ ] UI
- [X] API
- [ ] Docs
- [ ] Collection
- [ ] CLI
- [ ] Other

### Installation method

kubernetes

### Modifications

no

### Ansible version

N/A

### Operating system

CentOS 8 Stream

### Web browser

Chrome

### Steps to reproduce

1. Deploy AWX 21.7.0 on any platform
2. Add new instance and generate install bundle for the instance
3. Extract install bundle and ensure `Not After` in the `receptor.crt` by `openssl x509 -text -in receptor/tls/receptor.crt -noout`

### Expected results

Certificate and execution environment are valid for enough life time.

### Actual results

Not tested enough, I guess my execution environment will be expired in 10 days if I won't make any manual renewal for the certs.

### Additional information

@TheRealHaoLiu @jbradberry 
Thanks for excellent feature; execution node! It works as expected in my environment in this few days :)
Sorry for annoying you by this mention, but I want to confirm that this is designed or not.

I found that there is hard-coded `days=10` in your commit.
https://github.com/ansible/awx/blob/150c55c72a4d1d896474c9d3aaaceeb1b69ee253/awx/api/views/instance_install_bundle.py#L181

According to your comment on `awx-operator` repository, it seems that you've designed it as _10 years_ instead of _10 days_ I guess...?; https://github.com/ansible/awx-operator/pull/1012#issuecomment-1208423340

Please let me know it's not recommended that trying to use generated certs in the long term, or if you have an easier way to renew certs.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `awx/api/views/instance_install_bundle.py`
Content:
```
1 # Copyright (c) 2018 Red Hat, Inc.
2 # All Rights Reserved.
3 
4 import datetime
5 import io
6 import ipaddress
7 import os
8 import tarfile
9 
10 import asn1
11 from awx.api import serializers
12 from awx.api.generics import GenericAPIView, Response
13 from awx.api.permissions import IsSystemAdminOrAuditor
14 from awx.main import models
15 from cryptography import x509
16 from cryptography.hazmat.primitives import hashes, serialization
17 from cryptography.hazmat.primitives.asymmetric import rsa
18 from cryptography.x509 import DNSName, IPAddress, ObjectIdentifier, OtherName
19 from cryptography.x509.oid import NameOID
20 from django.http import HttpResponse
21 from django.template.loader import render_to_string
22 from django.utils.translation import gettext_lazy as _
23 from rest_framework import status
24 
25 # Red Hat has an OID namespace (RHANANA). Receptor has its own designation under that.
26 RECEPTOR_OID = "1.3.6.1.4.1.2312.19.1"
27 
28 # generate install bundle for the instance
29 # install bundle directory structure
30 # ├── install_receptor.yml (playbook)
31 # ├── inventory.yml
32 # ├── group_vars
33 # │   └── all.yml
34 # ├── receptor
35 # │   ├── tls
36 # │   │   ├── ca
37 # │   │   │   └── receptor-ca.crt
38 # │   │   ├── receptor.crt
39 # │   │   └── receptor.key
40 # │   └── work-public-key.pem
41 # └── requirements.yml
42 class InstanceInstallBundle(GenericAPIView):
43 
44     name = _('Install Bundle')
45     model = models.Instance
46     serializer_class = serializers.InstanceSerializer
47     permission_classes = (IsSystemAdminOrAuditor,)
48 
49     def get(self, request, *args, **kwargs):
50         instance_obj = self.get_object()
51 
52         if instance_obj.node_type not in ('execution',):
53             return Response(
54                 data=dict(msg=_('Install bundle can only be generated for execution nodes.')),
55                 status=status.HTTP_400_BAD_REQUEST,
56             )
57 
58         with io.BytesIO() as f:
59             with tarfile.open(fileobj=f, mode='w:gz') as tar:
60                 # copy /etc/receptor/tls/ca/receptor-ca.crt to receptor/tls/ca in the tar file
61                 tar.add(
62                     os.path.realpath('/etc/receptor/tls/ca/receptor-ca.crt'), arcname=f"{instance_obj.hostname}_install_bundle/receptor/tls/ca/receptor-ca.crt"
63                 )
64 
65                 # copy /etc/receptor/signing/work-public-key.pem to receptor/work-public-key.pem
66                 tar.add('/etc/receptor/signing/work-public-key.pem', arcname=f"{instance_obj.hostname}_install_bundle/receptor/work-public-key.pem")
67 
68                 # generate and write the receptor key to receptor/tls/receptor.key in the tar file
69                 key, cert = generate_receptor_tls(instance_obj)
70 
71                 key_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.key")
72                 key_tarinfo.size = len(key)
73                 tar.addfile(key_tarinfo, io.BytesIO(key))
74 
75                 cert_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.crt")
76                 cert_tarinfo.size = len(cert)
77                 tar.addfile(cert_tarinfo, io.BytesIO(cert))
78 
79                 # generate and write install_receptor.yml to the tar file
80                 playbook = generate_playbook().encode('utf-8')
81                 playbook_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/install_receptor.yml")
82                 playbook_tarinfo.size = len(playbook)
83                 tar.addfile(playbook_tarinfo, io.BytesIO(playbook))
84 
85                 # generate and write inventory.yml to the tar file
86                 inventory_yml = generate_inventory_yml(instance_obj).encode('utf-8')
87                 inventory_yml_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/inventory.yml")
88                 inventory_yml_tarinfo.size = len(inventory_yml)
89                 tar.addfile(inventory_yml_tarinfo, io.BytesIO(inventory_yml))
90 
91                 # generate and write group_vars/all.yml to the tar file
92                 group_vars = generate_group_vars_all_yml(instance_obj).encode('utf-8')
93                 group_vars_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/group_vars/all.yml")
94                 group_vars_tarinfo.size = len(group_vars)
95                 tar.addfile(group_vars_tarinfo, io.BytesIO(group_vars))
96 
97                 # generate and write requirements.yml to the tar file
98                 requirements_yml = generate_requirements_yml().encode('utf-8')
99                 requirements_yml_tarinfo = tarfile.TarInfo(f"{instance_obj.hostname}_install_bundle/requirements.yml")
100                 requirements_yml_tarinfo.size = len(requirements_yml)
101                 tar.addfile(requirements_yml_tarinfo, io.BytesIO(requirements_yml))
102 
103             # respond with the tarfile
104             f.seek(0)
105             response = HttpResponse(f.read(), status=status.HTTP_200_OK)
106             response['Content-Disposition'] = f"attachment; filename={instance_obj.hostname}_install_bundle.tar.gz"
107             return response
108 
109 
110 def generate_playbook():
111     return render_to_string("instance_install_bundle/install_receptor.yml")
112 
113 
114 def generate_requirements_yml():
115     return render_to_string("instance_install_bundle/requirements.yml")
116 
117 
118 def generate_inventory_yml(instance_obj):
119     return render_to_string("instance_install_bundle/inventory.yml", context=dict(instance=instance_obj))
120 
121 
122 def generate_group_vars_all_yml(instance_obj):
123     return render_to_string("instance_install_bundle/group_vars/all.yml", context=dict(instance=instance_obj))
124 
125 
126 def generate_receptor_tls(instance_obj):
127     # generate private key for the receptor
128     key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
129 
130     # encode receptor hostname to asn1
131     hostname = instance_obj.hostname
132     encoder = asn1.Encoder()
133     encoder.start()
134     encoder.write(hostname.encode(), nr=asn1.Numbers.UTF8String)
135     hostname_asn1 = encoder.output()
136 
137     san_params = [
138         DNSName(hostname),
139         OtherName(ObjectIdentifier(RECEPTOR_OID), hostname_asn1),
140     ]
141 
142     try:
143         san_params.append(IPAddress(ipaddress.IPv4Address(hostname)))
144     except ipaddress.AddressValueError:
145         pass
146 
147     # generate certificate for the receptor
148     csr = (
149         x509.CertificateSigningRequestBuilder()
150         .subject_name(
151             x509.Name(
152                 [
153                     x509.NameAttribute(NameOID.COMMON_NAME, hostname),
154                 ]
155             )
156         )
157         .add_extension(
158             x509.SubjectAlternativeName(san_params),
159             critical=False,
160         )
161         .sign(key, hashes.SHA256())
162     )
163 
164     # sign csr with the receptor ca key from /etc/receptor/ca/receptor-ca.key
165     with open('/etc/receptor/tls/ca/receptor-ca.key', 'rb') as f:
166         ca_key = serialization.load_pem_private_key(
167             f.read(),
168             password=None,
169         )
170 
171     with open('/etc/receptor/tls/ca/receptor-ca.crt', 'rb') as f:
172         ca_cert = x509.load_pem_x509_certificate(f.read())
173 
174     cert = (
175         x509.CertificateBuilder()
176         .subject_name(csr.subject)
177         .issuer_name(ca_cert.issuer)
178         .public_key(csr.public_key())
179         .serial_number(x509.random_serial_number())
180         .not_valid_before(datetime.datetime.utcnow())
181         .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))
182         .add_extension(
183             csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value,
184             critical=csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical,
185         )
186         .sign(ca_key, hashes.SHA256())
187     )
188 
189     key = key.private_bytes(
190         encoding=serialization.Encoding.PEM,
191         format=serialization.PrivateFormat.TraditionalOpenSSL,
192         encryption_algorithm=serialization.NoEncryption(),
193     )
194 
195     cert = cert.public_bytes(
196         encoding=serialization.Encoding.PEM,
197     )
198 
199     return key, cert
200
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/awx/api/views/instance_install_bundle.py b/awx/api/views/instance_install_bundle.py
--- a/awx/api/views/instance_install_bundle.py
+++ b/awx/api/views/instance_install_bundle.py
@@ -178,7 +178,7 @@
         .public_key(csr.public_key())
         .serial_number(x509.random_serial_number())
         .not_valid_before(datetime.datetime.utcnow())
-        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))
+        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))
         .add_extension(
             csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value,
             critical=csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical,

{"golden_diff": "diff --git a/awx/api/views/instance_install_bundle.py b/awx/api/views/instance_install_bundle.py\n--- a/awx/api/views/instance_install_bundle.py\n+++ b/awx/api/views/instance_install_bundle.py\n@@ -178,7 +178,7 @@\n         .public_key(csr.public_key())\n         .serial_number(x509.random_serial_number())\n         .not_valid_before(datetime.datetime.utcnow())\n-        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))\n+        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))\n         .add_extension(\n             csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value,\n             critical=csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical,\n", "issue": "Genrated certificate in install bundle for Execution node is valid only for 10 days\n### Please confirm the following\r\n\r\n- [X] I agree to follow this project's [code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).\r\n- [X] I have checked the [current issues](https://github.com/ansible/awx/issues) for duplicates.\r\n- [X] I understand that AWX is open source software provided for free and that I might not receive a timely response.\r\n\r\n### Bug Summary\r\n\r\nThe certificate for receptor in install bundle (`*.tar.gz`) for execution node that generated by AWX is valid only for 10 days.\r\n\r\nIn my understanding currently there is no automated certificate renewal feature in both AWX and Receptor, so I guess the execution environment will be invalidated after 10 days. \r\n\r\nTherefore frequently renewal (generating bundle and invoke playbook again) by hand might be required.\r\n\r\n```bash\r\n$ ls -l\r\ntotal 24\r\n-rw-rw-r--. 1 kuro kuro 11278 Oct  7 05:12 ansible.log\r\ndrwxrwxr-x. 2 kuro kuro    21 Oct  7 05:09 group_vars\r\n-rw-r--r--. 1 kuro kuro   406 Jan  1  1970 install_receptor.yml\r\n-rw-r--r--. 1 kuro kuro   159 Oct  7 05:10 inventory.yml\r\ndrwxrwxr-x. 3 kuro kuro    44 Oct  7 05:09 receptor\r\n-rw-r--r--. 1 kuro kuro   137 Jan  1  1970 requirements.yml\r\n\r\n$ openssl x509 -text -in receptor/tls/receptor.crt -noout | grep Not\r\n            Not Before: Oct  6 20:09:21 2022 GMT\r\n            Not After : Oct 16 20:09:21 2022 GMT     \ud83d\udc48\ud83d\udc48\ud83d\udc48\r\n```\r\n\r\nInstaller (playbook) just place certificate on execution node without any modification, so deployed execution node uses certificate that included in the install bandle as is. \r\n\r\nI have no idea if this is a _bug_ or _designed behavior_, but just in case I'm creating this issue.\r\n\r\n### AWX version\r\n\r\n21.7.0\r\n\r\n### Select the relevant components\r\n\r\n- [ ] UI\r\n- [X] API\r\n- [ ] Docs\r\n- [ ] Collection\r\n- [ ] CLI\r\n- [ ] Other\r\n\r\n### Installation method\r\n\r\nkubernetes\r\n\r\n### Modifications\r\n\r\nno\r\n\r\n### Ansible version\r\n\r\nN/A\r\n\r\n### Operating system\r\n\r\nCentOS 8 Stream\r\n\r\n### Web browser\r\n\r\nChrome\r\n\r\n### Steps to reproduce\r\n\r\n1. Deploy AWX 21.7.0 on any platform\r\n2. Add new instance and generate install bundle for the instance\r\n3. Extract install bundle and ensure `Not After` in the `receptor.crt` by `openssl x509 -text -in receptor/tls/receptor.crt -noout`\r\n\r\n### Expected results\r\n\r\nCertificate and execution environment are valid for enough life time.\r\n\r\n### Actual results\r\n\r\nNot tested enough, I guess my execution environment will be expired in 10 days if I won't make any manual renewal for the certs.\r\n\r\n### Additional information\r\n\r\n@TheRealHaoLiu @jbradberry \r\nThanks for excellent feature; execution node! It works as expected in my environment in this few days :)\r\nSorry for annoying you by this mention, but I want to confirm that this is designed or not.\r\n\r\nI found that there is hard-coded `days=10` in your commit.\r\nhttps://github.com/ansible/awx/blob/150c55c72a4d1d896474c9d3aaaceeb1b69ee253/awx/api/views/instance_install_bundle.py#L181\r\n\r\nAccording to your comment on `awx-operator` repository, it seems that you've designed it as _10 years_ instead of _10 days_ I guess...?; https://github.com/ansible/awx-operator/pull/1012#issuecomment-1208423340\r\n\r\nPlease let me know it's not recommended that trying to use generated certs in the long term, or if you have an easier way to renew certs.\n", "before_files": [{"content": "# Copyright (c) 2018 Red Hat, Inc.\n# All Rights Reserved.\n\nimport datetime\nimport io\nimport ipaddress\nimport os\nimport tarfile\n\nimport asn1\nfrom awx.api import serializers\nfrom awx.api.generics import GenericAPIView, Response\nfrom awx.api.permissions import IsSystemAdminOrAuditor\nfrom awx.main import models\nfrom cryptography import x509\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.x509 import DNSName, IPAddress, ObjectIdentifier, OtherName\nfrom cryptography.x509.oid import NameOID\nfrom django.http import HttpResponse\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import status\n\n# Red Hat has an OID namespace (RHANANA). Receptor has its own designation under that.\nRECEPTOR_OID = \"1.3.6.1.4.1.2312.19.1\"\n\n# generate install bundle for the instance\n# install bundle directory structure\n# \u251c\u2500\u2500 install_receptor.yml (playbook)\n# \u251c\u2500\u2500 inventory.yml\n# \u251c\u2500\u2500 group_vars\n# \u2502   \u2514\u2500\u2500 all.yml\n# \u251c\u2500\u2500 receptor\n# \u2502   \u251c\u2500\u2500 tls\n# \u2502   \u2502   \u251c\u2500\u2500 ca\n# \u2502   \u2502   \u2502   \u2514\u2500\u2500 receptor-ca.crt\n# \u2502   \u2502   \u251c\u2500\u2500 receptor.crt\n# \u2502   \u2502   \u2514\u2500\u2500 receptor.key\n# \u2502   \u2514\u2500\u2500 work-public-key.pem\n# \u2514\u2500\u2500 requirements.yml\nclass InstanceInstallBundle(GenericAPIView):\n\n    name = _('Install Bundle')\n    model = models.Instance\n    serializer_class = serializers.InstanceSerializer\n    permission_classes = (IsSystemAdminOrAuditor,)\n\n    def get(self, request, *args, **kwargs):\n        instance_obj = self.get_object()\n\n        if instance_obj.node_type not in ('execution',):\n            return Response(\n                data=dict(msg=_('Install bundle can only be generated for execution nodes.')),\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n\n        with io.BytesIO() as f:\n            with tarfile.open(fileobj=f, mode='w:gz') as tar:\n                # copy /etc/receptor/tls/ca/receptor-ca.crt to receptor/tls/ca in the tar file\n                tar.add(\n                    os.path.realpath('/etc/receptor/tls/ca/receptor-ca.crt'), arcname=f\"{instance_obj.hostname}_install_bundle/receptor/tls/ca/receptor-ca.crt\"\n                )\n\n                # copy /etc/receptor/signing/work-public-key.pem to receptor/work-public-key.pem\n                tar.add('/etc/receptor/signing/work-public-key.pem', arcname=f\"{instance_obj.hostname}_install_bundle/receptor/work-public-key.pem\")\n\n                # generate and write the receptor key to receptor/tls/receptor.key in the tar file\n                key, cert = generate_receptor_tls(instance_obj)\n\n                key_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.key\")\n                key_tarinfo.size = len(key)\n                tar.addfile(key_tarinfo, io.BytesIO(key))\n\n                cert_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.crt\")\n                cert_tarinfo.size = len(cert)\n                tar.addfile(cert_tarinfo, io.BytesIO(cert))\n\n                # generate and write install_receptor.yml to the tar file\n                playbook = generate_playbook().encode('utf-8')\n                playbook_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/install_receptor.yml\")\n                playbook_tarinfo.size = len(playbook)\n                tar.addfile(playbook_tarinfo, io.BytesIO(playbook))\n\n                # generate and write inventory.yml to the tar file\n                inventory_yml = generate_inventory_yml(instance_obj).encode('utf-8')\n                inventory_yml_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/inventory.yml\")\n                inventory_yml_tarinfo.size = len(inventory_yml)\n                tar.addfile(inventory_yml_tarinfo, io.BytesIO(inventory_yml))\n\n                # generate and write group_vars/all.yml to the tar file\n                group_vars = generate_group_vars_all_yml(instance_obj).encode('utf-8')\n                group_vars_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/group_vars/all.yml\")\n                group_vars_tarinfo.size = len(group_vars)\n                tar.addfile(group_vars_tarinfo, io.BytesIO(group_vars))\n\n                # generate and write requirements.yml to the tar file\n                requirements_yml = generate_requirements_yml().encode('utf-8')\n                requirements_yml_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/requirements.yml\")\n                requirements_yml_tarinfo.size = len(requirements_yml)\n                tar.addfile(requirements_yml_tarinfo, io.BytesIO(requirements_yml))\n\n            # respond with the tarfile\n            f.seek(0)\n            response = HttpResponse(f.read(), status=status.HTTP_200_OK)\n            response['Content-Disposition'] = f\"attachment; filename={instance_obj.hostname}_install_bundle.tar.gz\"\n            return response\n\n\ndef generate_playbook():\n    return render_to_string(\"instance_install_bundle/install_receptor.yml\")\n\n\ndef generate_requirements_yml():\n    return render_to_string(\"instance_install_bundle/requirements.yml\")\n\n\ndef generate_inventory_yml(instance_obj):\n    return render_to_string(\"instance_install_bundle/inventory.yml\", context=dict(instance=instance_obj))\n\n\ndef generate_group_vars_all_yml(instance_obj):\n    return render_to_string(\"instance_install_bundle/group_vars/all.yml\", context=dict(instance=instance_obj))\n\n\ndef generate_receptor_tls(instance_obj):\n    # generate private key for the receptor\n    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)\n\n    # encode receptor hostname to asn1\n    hostname = instance_obj.hostname\n    encoder = asn1.Encoder()\n    encoder.start()\n    encoder.write(hostname.encode(), nr=asn1.Numbers.UTF8String)\n    hostname_asn1 = encoder.output()\n\n    san_params = [\n        DNSName(hostname),\n        OtherName(ObjectIdentifier(RECEPTOR_OID), hostname_asn1),\n    ]\n\n    try:\n        san_params.append(IPAddress(ipaddress.IPv4Address(hostname)))\n    except ipaddress.AddressValueError:\n        pass\n\n    # generate certificate for the receptor\n    csr = (\n        x509.CertificateSigningRequestBuilder()\n        .subject_name(\n            x509.Name(\n                [\n                    x509.NameAttribute(NameOID.COMMON_NAME, hostname),\n                ]\n            )\n        )\n        .add_extension(\n            x509.SubjectAlternativeName(san_params),\n            critical=False,\n        )\n        .sign(key, hashes.SHA256())\n    )\n\n    # sign csr with the receptor ca key from /etc/receptor/ca/receptor-ca.key\n    with open('/etc/receptor/tls/ca/receptor-ca.key', 'rb') as f:\n        ca_key = serialization.load_pem_private_key(\n            f.read(),\n            password=None,\n        )\n\n    with open('/etc/receptor/tls/ca/receptor-ca.crt', 'rb') as f:\n        ca_cert = x509.load_pem_x509_certificate(f.read())\n\n    cert = (\n        x509.CertificateBuilder()\n        .subject_name(csr.subject)\n        .issuer_name(ca_cert.issuer)\n        .public_key(csr.public_key())\n        .serial_number(x509.random_serial_number())\n        .not_valid_before(datetime.datetime.utcnow())\n        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=10))\n        .add_extension(\n            csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value,\n            critical=csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical,\n        )\n        .sign(ca_key, hashes.SHA256())\n    )\n\n    key = key.private_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PrivateFormat.TraditionalOpenSSL,\n        encryption_algorithm=serialization.NoEncryption(),\n    )\n\n    cert = cert.public_bytes(\n        encoding=serialization.Encoding.PEM,\n    )\n\n    return key, cert\n", "path": "awx/api/views/instance_install_bundle.py"}], "after_files": [{"content": "# Copyright (c) 2018 Red Hat, Inc.\n# All Rights Reserved.\n\nimport datetime\nimport io\nimport ipaddress\nimport os\nimport tarfile\n\nimport asn1\nfrom awx.api import serializers\nfrom awx.api.generics import GenericAPIView, Response\nfrom awx.api.permissions import IsSystemAdminOrAuditor\nfrom awx.main import models\nfrom cryptography import x509\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.x509 import DNSName, IPAddress, ObjectIdentifier, OtherName\nfrom cryptography.x509.oid import NameOID\nfrom django.http import HttpResponse\nfrom django.template.loader import render_to_string\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import status\n\n# Red Hat has an OID namespace (RHANANA). Receptor has its own designation under that.\nRECEPTOR_OID = \"1.3.6.1.4.1.2312.19.1\"\n\n# generate install bundle for the instance\n# install bundle directory structure\n# \u251c\u2500\u2500 install_receptor.yml (playbook)\n# \u251c\u2500\u2500 inventory.yml\n# \u251c\u2500\u2500 group_vars\n# \u2502   \u2514\u2500\u2500 all.yml\n# \u251c\u2500\u2500 receptor\n# \u2502   \u251c\u2500\u2500 tls\n# \u2502   \u2502   \u251c\u2500\u2500 ca\n# \u2502   \u2502   \u2502   \u2514\u2500\u2500 receptor-ca.crt\n# \u2502   \u2502   \u251c\u2500\u2500 receptor.crt\n# \u2502   \u2502   \u2514\u2500\u2500 receptor.key\n# \u2502   \u2514\u2500\u2500 work-public-key.pem\n# \u2514\u2500\u2500 requirements.yml\nclass InstanceInstallBundle(GenericAPIView):\n\n    name = _('Install Bundle')\n    model = models.Instance\n    serializer_class = serializers.InstanceSerializer\n    permission_classes = (IsSystemAdminOrAuditor,)\n\n    def get(self, request, *args, **kwargs):\n        instance_obj = self.get_object()\n\n        if instance_obj.node_type not in ('execution',):\n            return Response(\n                data=dict(msg=_('Install bundle can only be generated for execution nodes.')),\n                status=status.HTTP_400_BAD_REQUEST,\n            )\n\n        with io.BytesIO() as f:\n            with tarfile.open(fileobj=f, mode='w:gz') as tar:\n                # copy /etc/receptor/tls/ca/receptor-ca.crt to receptor/tls/ca in the tar file\n                tar.add(\n                    os.path.realpath('/etc/receptor/tls/ca/receptor-ca.crt'), arcname=f\"{instance_obj.hostname}_install_bundle/receptor/tls/ca/receptor-ca.crt\"\n                )\n\n                # copy /etc/receptor/signing/work-public-key.pem to receptor/work-public-key.pem\n                tar.add('/etc/receptor/signing/work-public-key.pem', arcname=f\"{instance_obj.hostname}_install_bundle/receptor/work-public-key.pem\")\n\n                # generate and write the receptor key to receptor/tls/receptor.key in the tar file\n                key, cert = generate_receptor_tls(instance_obj)\n\n                key_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.key\")\n                key_tarinfo.size = len(key)\n                tar.addfile(key_tarinfo, io.BytesIO(key))\n\n                cert_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/receptor/tls/receptor.crt\")\n                cert_tarinfo.size = len(cert)\n                tar.addfile(cert_tarinfo, io.BytesIO(cert))\n\n                # generate and write install_receptor.yml to the tar file\n                playbook = generate_playbook().encode('utf-8')\n                playbook_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/install_receptor.yml\")\n                playbook_tarinfo.size = len(playbook)\n                tar.addfile(playbook_tarinfo, io.BytesIO(playbook))\n\n                # generate and write inventory.yml to the tar file\n                inventory_yml = generate_inventory_yml(instance_obj).encode('utf-8')\n                inventory_yml_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/inventory.yml\")\n                inventory_yml_tarinfo.size = len(inventory_yml)\n                tar.addfile(inventory_yml_tarinfo, io.BytesIO(inventory_yml))\n\n                # generate and write group_vars/all.yml to the tar file\n                group_vars = generate_group_vars_all_yml(instance_obj).encode('utf-8')\n                group_vars_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/group_vars/all.yml\")\n                group_vars_tarinfo.size = len(group_vars)\n                tar.addfile(group_vars_tarinfo, io.BytesIO(group_vars))\n\n                # generate and write requirements.yml to the tar file\n                requirements_yml = generate_requirements_yml().encode('utf-8')\n                requirements_yml_tarinfo = tarfile.TarInfo(f\"{instance_obj.hostname}_install_bundle/requirements.yml\")\n                requirements_yml_tarinfo.size = len(requirements_yml)\n                tar.addfile(requirements_yml_tarinfo, io.BytesIO(requirements_yml))\n\n            # respond with the tarfile\n            f.seek(0)\n            response = HttpResponse(f.read(), status=status.HTTP_200_OK)\n            response['Content-Disposition'] = f\"attachment; filename={instance_obj.hostname}_install_bundle.tar.gz\"\n            return response\n\n\ndef generate_playbook():\n    return render_to_string(\"instance_install_bundle/install_receptor.yml\")\n\n\ndef generate_requirements_yml():\n    return render_to_string(\"instance_install_bundle/requirements.yml\")\n\n\ndef generate_inventory_yml(instance_obj):\n    return render_to_string(\"instance_install_bundle/inventory.yml\", context=dict(instance=instance_obj))\n\n\ndef generate_group_vars_all_yml(instance_obj):\n    return render_to_string(\"instance_install_bundle/group_vars/all.yml\", context=dict(instance=instance_obj))\n\n\ndef generate_receptor_tls(instance_obj):\n    # generate private key for the receptor\n    key = rsa.generate_private_key(public_exponent=65537, key_size=2048)\n\n    # encode receptor hostname to asn1\n    hostname = instance_obj.hostname\n    encoder = asn1.Encoder()\n    encoder.start()\n    encoder.write(hostname.encode(), nr=asn1.Numbers.UTF8String)\n    hostname_asn1 = encoder.output()\n\n    san_params = [\n        DNSName(hostname),\n        OtherName(ObjectIdentifier(RECEPTOR_OID), hostname_asn1),\n    ]\n\n    try:\n        san_params.append(IPAddress(ipaddress.IPv4Address(hostname)))\n    except ipaddress.AddressValueError:\n        pass\n\n    # generate certificate for the receptor\n    csr = (\n        x509.CertificateSigningRequestBuilder()\n        .subject_name(\n            x509.Name(\n                [\n                    x509.NameAttribute(NameOID.COMMON_NAME, hostname),\n                ]\n            )\n        )\n        .add_extension(\n            x509.SubjectAlternativeName(san_params),\n            critical=False,\n        )\n        .sign(key, hashes.SHA256())\n    )\n\n    # sign csr with the receptor ca key from /etc/receptor/ca/receptor-ca.key\n    with open('/etc/receptor/tls/ca/receptor-ca.key', 'rb') as f:\n        ca_key = serialization.load_pem_private_key(\n            f.read(),\n            password=None,\n        )\n\n    with open('/etc/receptor/tls/ca/receptor-ca.crt', 'rb') as f:\n        ca_cert = x509.load_pem_x509_certificate(f.read())\n\n    cert = (\n        x509.CertificateBuilder()\n        .subject_name(csr.subject)\n        .issuer_name(ca_cert.issuer)\n        .public_key(csr.public_key())\n        .serial_number(x509.random_serial_number())\n        .not_valid_before(datetime.datetime.utcnow())\n        .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=3650))\n        .add_extension(\n            csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value,\n            critical=csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical,\n        )\n        .sign(ca_key, hashes.SHA256())\n    )\n\n    key = key.private_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PrivateFormat.TraditionalOpenSSL,\n        encryption_algorithm=serialization.NoEncryption(),\n    )\n\n    cert = cert.public_bytes(\n        encoding=serialization.Encoding.PEM,\n    )\n\n    return key, cert\n", "path": "awx/api/views/instance_install_bundle.py"}]}