{"id":4691,"date":"2022-10-27T00:33:02","date_gmt":"2022-10-27T00:33:02","guid":{"rendered":"https:\/\/dalelane.co.uk\/blog\/?p=4691"},"modified":"2022-10-27T00:46:22","modified_gmt":"2022-10-27T00:46:22","slug":"setting-up-trusted-ssl-for-ibm-event-streams","status":"publish","type":"post","link":"https:\/\/dalelane.co.uk\/blog\/?p=4691","title":{"rendered":"Setting up trusted SSL for IBM Event Streams"},"content":{"rendered":"<p><strong>A quick how-to for setting up <a href=\"https:\/\/ibm.github.io\/event-streams\/\">Event Streams<\/a> with trusted certificates when running a development project.<\/strong><\/p>\n<h2>Problem<\/h2>\n<p>You&#8217;re working on a project using IBM Event Streams. It&#8217;s just a development project, so you&#8217;re not using an SSL certificate signed by your real, trusted, corporate signer.<\/p>\n<p>Everything works, but&#8230;<\/p>\n<p>You get errors like these every time you access the web tooling &#8211; which you have to click through.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/github.com\/dalelane\/eventstreams-valid-ssl\/raw\/master\/screenshots\/screenshot-chrome.png\" style=\"max-width: 900px; width: 98%;\"\/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/github.com\/dalelane\/eventstreams-valid-ssl\/raw\/master\/screenshots\/screenshot-safari.png\" style=\"max-width: 900px; width: 98%;\"\/><\/p>\n<p>And you get errors like these from your Kafka client applications &#8211; which you have to configure with a custom truststore to avoid (<em>although, if you do need to do that, I have <a href=\"https:\/\/dalelane.co.uk\/blog\/?p=4399\">a guide to help<\/a>!<\/em>)<\/p>\n<pre class=\"codeplain\" style=\"max-height: 250px; max-width: 90%; padding: 10px; overflow: scroll; overflow-x: scroll; border: thin black solid; font-size: smaller; background-color: #eeeeee; color: black; margin-left: 16px;\">[2021-06-27 23:19:06,048] ERROR [Consumer clientId=consumer-dalegrp-1, groupId=dalegrp] Connection to node -1 (dale-kafka-saslscram-bootstrap-strimzi.apps.eem-test-fest-6.cp.fyre.ibm.com\/9.46.199.58:443) failed authentication due to: SSL handshake failed (org.apache.kafka.clients.NetworkClient)\n[2021-06-27 23:19:06,049] WARN [Consumer clientId=consumer-dalegrp-1, groupId=dalegrp] Bootstrap broker dale-kafka-saslscram-bootstrap-strimzi.apps.eem-test-fest-6.cp.fyre.ibm.com:443 (id: -1 rack: null) disconnected (org.apache.kafka.clients.NetworkClient)\n[2021-06-27 23:19:06,069] ERROR Error processing message, terminating consumer process:  (kafka.tools.ConsoleConsumer$)\norg.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed\nCaused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target\n\tat java.base\/sun.security.ssl.Alert.createSSLException(Alert.java:131)\n\tat java.base\/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)\n\tat java.base\/sun.security.ssl.TransportContext.fatal(TransportContext.java:269)\n\tat java.base\/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)\n\tat java.base\/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1339)\n\tat java.base\/sun.security.ssl.CertificateMessage$T13CertificateConsumer.onConsumeCertificate(CertificateMessage.java:1214)\n\tat java.base\/sun.security.ssl.CertificateMessage$T13CertificateConsumer.consume(CertificateMessage.java:1157)\n\tat java.base\/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)\n\tat java.base\/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)\n\tat java.base\/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1074)\n\tat java.base\/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1061)\n\tat java.base\/java.security.AccessController.doPrivileged(AccessController.java:770)\n\tat java.base\/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1008)\n<\/pre>\n<p><!--more--><\/p>\n<h2>Explanation<\/h2>\n<p>The reason for these errors is because by default, Cloud Pak for Integration &#8211; in the absence of a certificate you&#8217;d rather use &#8211; has generated new self-signed certificates to use.<\/p>\n<p>Your browser and client applications don&#8217;t trust this new self-signed certificate by default.<\/p>\n<h2>Solution<\/h2>\n<p><strong>TLDR: Use a free certificate from <a href=\"https:\/\/letsencrypt.org\">Let&#8217;s Encrypt<\/a>. When you set up Event Streams, configure it to use that certificate. That will (almost certainly) be trusted by all of your apps, and you won&#8217;t have to go through the annoying workarounds.<\/strong><\/p>\n<p>The rest of this post is just a guided walkthrough explaining how to do that.<\/p>\n<p>To keep things simple, I&#8217;m going to assume that you&#8217;re starting from scratch &#8211; you have a brand new, vanilla, OpenShift cluster.<\/p>\n<ul>\n<li><a href=\"#step-1\">1 &#8211; Get a certificate<\/a><\/li>\n<li><a href=\"#step-2\">2 &#8211; Install the CP4I operators<\/a><\/li>\n<li><a href=\"#step-3\">3 &#8211; Prepare the namespace<\/a><\/li>\n<li><a href=\"#step-4\">4 &#8211; Start setting up Foundational Services<\/a><\/li>\n<li><a href=\"#step-5\">5 &#8211; Set up ingress using your certificate<\/a><\/li>\n<li><a href=\"#step-6\">6 &#8211; Create CP4I components<\/a><\/li>\n<li><a href=\"#step-7\">7 &#8211; Verify<\/a><\/li>\n<\/ul>\n<h3>Note:<\/h3>\n<p>Some of the commands in this post use files that you can find at<br \/>\n<a href=\"https:\/\/github.com\/dalelane\/eventstreams-valid-ssl\">github.com\/dalelane\/eventstreams-valid-ssl<\/a>.<\/p>\n<pre class=\"codehighlight\">Commands you should run will be highlighted like this<\/pre>\n<pre class=\"codeplain\">Commands to see what is happening will be shown like this<\/pre>\n<p><a name=\"step-1\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 1<\/small><br \/>\nGet a certificate<\/h2>\n<h3>ROKS<\/h3>\n<p>If you are using a <a href=\"https:\/\/www.ibm.com\/uk-en\/cloud\/openshift\">managed OpenShift cluster in IBM Cloud<\/a> (&#8220;ROKS&#8221;), a certificate is automatically created for you when you create the cluster. It&#8217;s already available as a <code>Secret<\/code> in the <code>openshift-ingress<\/code> namespace, and you can use that.<\/p>\n<pre class=\"codehighlight\">SECRET_NAME=$(oc get secret -n openshift-ingress  -o=jsonpath='{.items[?(@.metadata.annotations.ingress\\.cloud\\.ibm\\.com\/cert-source==\"ibm\")].metadata.name}')\n\necho \"Let's Encrypt certificate is available in the $SECRET_NAME secret in the openshift-ingress namespace\"\n<\/pre>\n<p>You can download this to a few files on your computer.<\/p>\n<p>First, the key:<\/p>\n<pre class=\"codehighlight\">oc get secret -n openshift-ingress $SECRET_NAME -o=jsonpath=\"{.data['tls\\.key']}\" | base64 -d &gt; tls.key\n<\/pre>\n<p>Then the full chain, which you&#8217;ll need to split into the certificate and the CA&#8217;s.<\/p>\n<pre class=\"codehighlight\">oc get secret -n openshift-ingress $SECRET_NAME -o=jsonpath=\"{.data['tls\\.crt']}\" | base64 -d &gt; full-chain.crt\n\nsplit -p \"-----BEGIN CERTIFICATE-----\" full-chain.crt cert-\n\nmv cert-aa tls.crt\n\ncat cert-ab cert-ac &gt; ca.crt\n\nrm cert-ab cert-ac\n<\/pre>\n<p>You should now have three files:<br \/>\n&#8211; tls.key<br \/>\n&#8211; tls.crt<br \/>\n&#8211; ca.crt<\/p>\n<h3>Other<\/h3>\n<p>If you&#8217;re running OpenShift somewhere else, you will need to create the certificate yourself. See <a href=\"https:\/\/www.ibm.com\/docs\/en\/cloud-paks\/cp-integration\/2022.2?topic=certificates-creating-custom-hostname\">the documentation<\/a> for more guidance.<\/p>\n<p><a name=\"step-2\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 2<\/small><br \/>\nInstall the Cloud Pak for Integration operators<\/h2>\n<p>Add the IBM software catalog to your OpenShift cluster.<\/p>\n<pre class=\"codehighlight\">oc apply -f 01-ibm-catalog-source.yaml\n<\/pre>\n<p>Wait for the operator catalog to be updated with the IBM operators, then install the Cloud Pak for Integration operator.<\/p>\n<pre class=\"codehighlight\">oc apply -f 02-operator-platform-navigator.yaml\n<\/pre>\n<p>Wait for the IBM Cloud Pak for Integration operator and it&#8217;s pre-reqs (IBM Cloud Pak foundational services, and IBM NamespaceScope Operator) finish installing.<\/p>\n<p><a name=\"step-3\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 3<\/small><br \/>\nPrepare the namespace<\/h2>\n<p>I&#8217;m going to use a namespace called <code>integration<\/code> in this walkthrough. You can obviously change this to anything you&#8217;d like in all the subsequent commands.<\/p>\n<p>Create the namespace.<\/p>\n<pre class=\"codehighlight\">oc new-project integration\n<\/pre>\n<p>Create an entitled registry key in the namespace, that you&#8217;ll need to deploy IBM software.<\/p>\n<pre class=\"codehighlight\">oc create secret docker-registry ibm-entitlement-key \\\n    --docker-username=cp \\\n    --docker-password=$IBM_ENTITLEMENT_KEY \\\n    --docker-server=cp.icr.io \\\n    --namespace=integration --dry-run=client -o yaml | oc apply -f -\n<\/pre>\n<p><a name=\"step-4\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 4<\/small><br \/>\nStart creating Cloud Pak Foundational Services<\/h2>\n<p>Cloud Pak Foundational Services provides a common front door for a lot of the Cloud Pak capabilities, including Event Streams.<\/p>\n<p>The next thing to do is to ask Cloud Pak Foundational services for an ingress, that we can then start configuring with our custom certificate.<\/p>\n<pre class=\"codehighlight\">oc apply -f 03-management-ingress.yaml -n integration\n<\/pre>\n<p>This will take a couple of minutes.<\/p>\n<pre class=\"codeplain\">% oc get operandrequest request-management-ingress\nNAME                         AGE   PHASE        CREATED AT\nrequest-management-ingress   58s   Installing   2022-10-26T07:58:49Z\n<\/pre>\n<p>Wait for the management ingress operator to finish installing.<\/p>\n<pre class=\"codeplain\">oc wait operandrequest request-management-ingress --for=condition=Ready --timeout=15m\n<\/pre>\n<pre class=\"codeplain\">% oc get operandrequest request-management-ingress\nNAME                         AGE   PHASE     CREATED AT\nrequest-management-ingress   50s   Running   2022-10-26T07:58:49Z\n<\/pre>\n<p>In the background, a variety of work will then start happening under the covers to set up the auth support. You should wait for this to complete. It can take ten minutes or so.<\/p>\n<p>Wait for the pods in the Common Services namespace to finish starting.<\/p>\n<pre class=\"codeplain\">oc get pods -n ibm-common-services\n<\/pre>\n<p>And wait for the setup jobs to complete.<\/p>\n<pre class=\"codeplain\">% oc get job -n ibm-common-services\nNAME                       COMPLETIONS   DURATION   AGE\niam-onboarding             0\/1           9s         9s\noidc-client-registration   0\/1           7s         7s\nsecurity-onboarding        0\/1           10s        10s\n\n% oc get job -n ibm-common-services\nNAME                       COMPLETIONS   DURATION   AGE\noidc-client-registration   1\/1           9m25s      9m25s\niam-onboarding             1\/1           10m        10m\nsecurity-onboarding        1\/1           10m        10m\n<\/pre>\n<p><a name=\"step-5\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 5<\/small><br \/>\nConfigure the ingress to use your certificate<\/h2>\n<p>The default management ingress will have created, and (using certmanager) will be managing, a route-cert certificate.<\/p>\n<p>You can find it in the Common Services namespace.<\/p>\n<pre class=\"codeplain\">% oc get certificates.certmanager.k8s.io route-cert -n ibm-common-services\nNAME         READY   SECRET             AGE   EXPIRATION\nroute-cert   True    route-tls-secret   12m   2023-10-26T08:05:47Z\n\n% oc get secret route-tls-secret -n ibm-common-services\nNAME               TYPE                DATA   AGE\nroute-tls-secret   kubernetes.io\/tls   3      6m31s\n<\/pre>\n<p>If you try and replace it, the Operator will immediately revert your changes, so the first step is to edit the management ingress to tell it to stop managing the certificate.<\/p>\n<pre class=\"codehighlight\">oc patch managementingress default \\\n    --type merge \\\n    --patch '{\"spec\":{\"ignoreRouteCert\":true}}' \\\n    -n ibm-common-services\n<\/pre>\n<p>Now that the Operator is ignoring the certificate, you&#8217;re free to delete it and replace it with your own custom certificate.<\/p>\n<p>Delete the certificate and secret.<\/p>\n<pre class=\"codehighlight\">oc delete certificates.certmanager.k8s.io route-cert -n ibm-common-services\noc delete secret route-tls-secret -n ibm-common-services\n<\/pre>\n<p>Replace the secret with a new one using your certificate.<\/p>\n<pre class=\"codehighlight\">oc create secret generic route-tls-secret -n ibm-common-services \\\n    --from-file=ca.crt --from-file=tls.crt --from-file=tls.key\n<\/pre>\n<p>There are a couple of things that don&#8217;t automatically notice that the certificate has changed, so you need to prompt them to do something.<\/p>\n<p>You should trigger some certificate secrets to be recreated by deleting them.<\/p>\n<pre class=\"codehighlight\">oc delete secret ibmcloud-cluster-ca-cert  -n ibm-common-services\noc delete secret management-ingress-ibmcloud-cluster-ca-cert  -n integration\n<\/pre>\n<p>And you should restart the auth pods so that they start using the new certificate.<\/p>\n<pre class=\"codehighlight\">oc delete pod -l app=auth-idp  -n ibm-common-services\n<\/pre>\n<p>They take a little while to restart, so wait for this to complete.<\/p>\n<pre class=\"codeplain\">oc wait --for condition=ready --timeout=900s pod -l app=auth-idp -n ibm-common-services\n<\/pre>\n<p><a name=\"step-6\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 6<\/small><br \/>\nCreate Cloud Pak for Integration resources using your certificate<\/h2>\n<h3>Platform Navigator<\/h3>\n<p>Create a new secret using your certificate files that the CP4I components can access.<\/p>\n<pre class=\"codehighlight\">oc create secret generic my-custom-cert -n integration \\\n    --from-file=ca.crt --from-file=tls.crt --from-file=tls.key\n<\/pre>\n<p>Create the common Platform Navigator UI.<\/p>\n<pre class=\"codehighlight\">oc apply -f 04-platform-navigator.yaml -n integration\n<\/pre>\n<p>Notice that it has been configured to use the custom certificate by including this:<\/p>\n<pre class=\"codeplain\">spec:\n  tls:\n    secretName: my-custom-cert\n<\/pre>\n<p>This takes a while to install for the first time, so you should wait for it to be ready.<\/p>\n<pre class=\"codeplain\">oc wait PlatformNavigator navigator --for=condition=Ready --timeout=1h -n integration\n<\/pre>\n<h3>Event Streams<\/h3>\n<p>Create a new secret with the certificate that includes the full chain, suitable for use by the Kafka listeners.<\/p>\n<pre class=\"codehighlight\">oc create secret tls my-kafka-listeners-cert \\\n    --cert=full-chain.crt \\\n    --key=tls.key \\\n    --dry-run=client -o json | \\\n    oc apply -n integration -f -\n<\/pre>\n<p>Install the Event Streams operator<\/p>\n<pre class=\"codehighlight\">oc apply -f 05-operator-event-streams.yaml\n<\/pre>\n<p>Create your Event Streams instance<\/p>\n<pre class=\"codehighlight\">cpconsoleroute=\"$(oc get route -n ibm-common-services cp-console -o jsonpath='{.spec.host}')\" \\\n    envsubst &lt; 06-event-streams.yaml | \\\n    oc apply -n integration -f -\n<\/pre>\n<p>Notice that the Kafka listeners are configured to use the full chain certificate, by including:<\/p>\n<pre class=\"codeplain\">spec:\n  strimziOverrides:\n    kafka:\n      listeners:\n        - tls: true\n          type: route\n          configuration:\n            brokerCertChainAndKey:\n              certificate: tls.crt\n              key: tls.key\n              secretName: my-kafka-listeners-cert\n          authentication:\n            type: scram-sha-512\n          name: external\n          port: 9094\n<\/pre>\n<p>And notice that it has been configured to use the custom single certificate for each of the different REST interfaces that Event Streams provides, by including:<\/p>\n<pre class=\"codeplain\">spec:\n  restProducer:\n    endpoints:\n      - name: restproducer\n        containerPort: 9080\n        certOverrides:\n          certificate: tls.crt\n          key: tls.key\n          secretName: my-custom-cert\n  apicurioRegistry:\n    endpoints:\n      - name: apicurio\n        containerPort: 9080\n        certOverrides:\n          certificate: tls.crt\n          key: tls.key\n          secretName: my-custom-cert\n  adminApi:\n    endpoints:\n      - name: adminapi\n        containerPort: 9080\n        certOverrides:\n          certificate: tls.crt\n          key: tls.key\n          secretName: my-custom-cert\n<\/pre>\n<p>The admin API also needs some additional configuration to tell it to use the external route (which is where the custom certificate is applied, using an OpenShift route set to reencrypt) instead of the default internal service.<\/p>\n<pre class=\"codeplain\">spec:\n  adminApi:\n    env:\n      - name: IAM_SERVER_URL\n        value: https:\/\/${cpconsoleroute}:443\n<\/pre>\n<p>The value for this comes from the foundational services route:<\/p>\n<pre class=\"codeplain\">oc get route -n ibm-common-services cp-console -o jsonpath='{.spec.host}'\n<\/pre>\n<p>Unfortunately, the Event Streams custom resource doesn&#8217;t support the normal Kubernetes valueFrom env, which is why I resort to <code>envsubst<\/code> for this.<\/p>\n<p>You could always edit the yaml file yourself if that is simpler &#8211; just remember to:<br \/>\n&#8211; add the <code>https:\/\/<\/code> bit at the start<br \/>\n&#8211; include the explicit port number 443 at the end<\/p>\n<p><a name=\"step-7\"><\/a><\/p>\n<h2 class=\"stepsection\"><small>Step 7<\/small><br \/>\nVerify<\/h2>\n<p>If it&#8217;s all worked, you should see the problems described above have gone.<\/p>\n<h3>Verify the Kafka listeners<\/h3>\n<p>Try creating a topic and credentials you can use with a Kafka application:<\/p>\n<pre class=\"codehighlight\">oc apply -f 07-kafka-topic.yaml -n integration\noc apply -f 08-kafka-credentials.yaml -n integration\n<\/pre>\n<p>You can then run your Kafka applications.<\/p>\n<pre class=\"codehighlight\">kafka-console-producer.sh \\\n    --bootstrap-server $(oc get eventstreams -n integration my-kafka-cluster -o jsonpath='{.status.kafkaListeners[0].bootstrapServers}') \\\n    --topic TEST \\\n    --producer-property \"sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username=testuser password=$(oc get secret testuser -nintegration -ojsonpath={.data.password} | base64 -d);\" \\\n    --producer-property 'sasl.mechanism=SCRAM-SHA-512' \\\n    --producer-property 'security.protocol=SASL_SSL'\n<\/pre>\n<p>No custom truststore or other system configuration changes needed!<\/p>\n<h3>Verify the web interfaces<\/h3>\n<p>Try visiting the web console:<\/p>\n<pre class=\"codeplain\">oc get eventstreams -n integration my-kafka-cluster -o jsonpath='{.status.endpoints[?(@.name==\"ui\")].uri}'\necho \"username : `oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_username}' | base64 -d`\"\necho \"password : `oc get secret -n ibm-common-services platform-auth-idp-credentials -o jsonpath='{.data.admin_password}' | base64 -d`\\n\"\n<\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/github.com\/dalelane\/eventstreams-valid-ssl\/raw\/master\/screenshots\/screenshot-valid.png\" style=\"max-width: 900px; width: 98%;\"\/><\/p>\n<p>No browser warnings!<\/p>\n<h2>Summary<\/h2>\n<p>With a little additional configuration, you can set up your Event Streams cluster using a trusted certificate that was automatically created for you. It means you can avoid the annoying workarounds that I often see developers putting up with in their development clusters.<\/p>\n<p>For more information, check:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.ibm.com\/docs\/en\/cloud-paks\/cp-integration\/2022.2?topic=certificates-creating-custom-hostname\">Cloud Pak for Integration docs<\/a><\/li>\n<li><a href=\"https:\/\/ibm.github.io\/event-streams\/installing\/configuring\/#providing-listener-certificates\">Event Streams docs<\/a><\/li>\n<\/ul>\n<p><em>Thanks to Tim Mitchell and Piers Walter for help with this post.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A quick how-to for setting up Event Streams with trusted certificates when running a development project. Problem You&#8217;re working on a project using IBM Event Streams. It&#8217;s just a development project, so you&#8217;re not using an SSL certificate signed by your real, trusted, corporate signer. Everything works, but&#8230; You get errors like these every time [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[593,582,583,584,608],"class_list":["post-4691","post","type-post","status-publish","format-standard","hentry","category-code","tag-apachekafka","tag-eventstreams","tag-ibmeventstreams","tag-kafka","tag-ssl"],"_links":{"self":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4691","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4691"}],"version-history":[{"count":0,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/4691\/revisions"}],"wp:attachment":[{"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4691"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4691"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dalelane.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4691"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}