{"id":1042,"date":"2017-08-23T21:25:43","date_gmt":"2017-08-24T02:25:43","guid":{"rendered":"https:\/\/www.softwareab.net\/wordpress\/?p=1042"},"modified":"2017-08-30T16:35:02","modified_gmt":"2017-08-30T21:35:02","slug":"kubernetes-dashboard-authentication-isolation","status":"publish","type":"post","link":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/","title":{"rendered":"Kubernetes Dashboard, Authentication, Isolation"},"content":{"rendered":"<p>Hello All. Today we are going to look at Kubernetes Dashboard, Authentication, and Isolation.<\/p>\n<h2>The Code<\/h2>\n<p>Let&#8217;s put the code up front; that way, if you don&#8217;t want to bother with the article you can start by poking around on your own. Example scripts and manifests are located at <a href=\"https:\/\/github.com\/andybrucenet\/kube-dex-dashboard.git\">the kube-dex-dashboard GitHub repo<\/a>.<\/p>\n<p>Still there? Great! Let&#8217;s talk about the problem&#8230;<\/p>\n<h2>The Problem<\/h2>\n<p>Per <a href=\"https:\/\/github.com\/kubernetes\/dashboard\/issues\/574\">this thread on GitHub<\/a> we had a problem; we had installed <a href=\"https:\/\/kubernetes.io\/docs\/setup\/independent\/create-cluster-kubeadm\/\">kubeadm<\/a> and configured the <a href=\"https:\/\/github.com\/kubernetes\/dashboard\">Kubernetes Dashboard<\/a>. Moreover, we used <a href=\"https:\/\/github.com\/coreos\/dex\">CoreOS dex<\/a> to integrate our backing <a href=\"https:\/\/www.freeipa.org\/page\/Main_Page\">FreeIPA<\/a> domain for authentication.<\/p>\n<p>The logic flow to demonstrate the problem is very simple:<\/p>\n<ol>\n<li>Use <code>kubectl proxy<\/code> to access dashboard<\/li>\n<li>In the dashboard, select an unpermitted namespace<\/li>\n<li>Create a deployment &#8211; it would be denied to the user when running kubectl from the CLI. But this same deployment is permitted under the dashboard.<\/li>\n<\/ol>\n<p>So that is <em>what<\/em> &#8211; now for <em>why<\/em>. The problem occurs because &#8211; out-of-the-box &#8211; Kubernetes Dashboard runs as a system-level process, normally with full cluster permissions. The issue arises when a user wants to authenticate and use the Dashboard &#8211; the user effectively runs as the same system identity that Dashboard uses.<\/p>\n<p>Effectively we could not let our users take advantage of the Kubernetes dashboard due to privilege escalation. Bummer.<\/p>\n<h2>The Solution<\/h2>\n<p>Our solution works around this problem by creating multiple Dashboards &#8211; one for each authorized user. It&#8217;s not pretty, it&#8217;s not particularly scalable, but it works.<\/p>\n<h3>Let&#8217;s Look at <strong>dex<\/strong>, AUTHN, and AUTHZ<\/h3>\n<p>Before we jump into the specific multi-dashboard setup, let&#8217;s start by looking at authentication for our cluster. <a href=\"https:\/\/kubernetes.io\/docs\/admin\/authentication\/\">Kubernetes Authentication<\/a> is implemented by the <a href=\"https:\/\/kubernetes.io\/docs\/admin\/kube-apiserver\/\">Kubernetes API Server<\/a>; this makes sense because commands issued via <a href=\"https:\/\/kubernetes.io\/docs\/user-guide\/kubectl-overview\/\">kubectl (the Kubernetes CLI)<\/a> execute against the API Server. So it follows that to configure authentication within Kubernetes, you will have specific options in your <code>\/etc\/kubernetes\/manifests\/kube-apiserver.yaml<\/code> manifest.<\/p>\n<h4>Kubernetes API Server Configuration and <code>dex<\/code><\/h4>\n<p>The following is how we configured the API server to delegate authentication and authorization to <code>dex<\/code>:<\/p>\n<pre><code>\r\nspec:\r\n  containers:\r\n  - command:\r\n    - kube-apiserver\r\n    [...various cruft...]\r\n    - --authorization-mode=RBAC\r\n    - --oidc-issuer-url=https:\/\/kubeadm-clu2.hlsdev.local:32000\r\n    - --oidc-client-id=[our-secret]\r\n    - --oidc-ca-file=[issuing-CA-key]\r\n    - --oidc-username-claim=sub\r\n    - --oidc-groups-claim=groups\r\n<\/code><\/pre>\n<p>Let&#8217;s quickly discuss the main settings (for gory details on setting up <code>dex<\/code> as an RBAC authenticator, see <a href=\"https:\/\/kubernetes.io\/docs\/admin\/authorization\/rbac\/\">Kubernetes RBAC Authentication setup<\/a>).<\/p>\n<ul>\n<li><code>--authorization-mode<\/code> &#8211; indicates we are using RBAC<\/li>\n<li><code>--oidc-issuer-url<\/code> &#8211; URI where the <code>dex<\/code> helper app is running<\/li>\n<li><code>--oidc-client-id<\/code> &#8211; shared secret that permits the <code>dex<\/code> helper app to communicate with the Kubernetes API Server during delegation<\/li>\n<li><code>--oidc-ca-file<\/code> &#8211; the CA that issues our certificates<\/li>\n<li><code>--oidc-username-claim<\/code> &#8211; as users are authenticated using the <code>dex<\/code> helper, a set of &#8220;claims&#8221; are returned. In our case, we map the <code>sub<\/code> claim to the username within the backing FreeIPA.<\/li>\n<li><code>--oidc-groups-claim<\/code> &#8211; we map the <code>groups<\/code> claim to the list of groups the authenticated user is a member of on the backing FreeIPA<\/li>\n<\/ul>\n<p>So the reason all of this matters is that our approach leverages permissions and group memberships to control access to Kubernetes API functions.<\/p>\n<h4>What an authentication looks like with <code>dex<\/code><\/h4>\n<p>Authentication using <code>dex<\/code> requires us to go through quite a few steps &#8211; all of which deserve an article of their own. Suffice it to say that a lot of <code>curl<\/code> commands are used in our shell script to setup the initial login, indicate the authorizations to use, and extract the all-important bearer ID. In our case, we have it all wrapped up so that we issue:<\/p>\n<pre><code>\r\nMacBook-Pro:~ l.abruce$ <strong>sab-k8s.sh dex-login<\/strong>\r\n<em>Enter password (abruce):\r\neyJhbGciOiJSUzI1NiIsImtpZCI6IjFiMmVhODYzYTJlMGI4Nzc4NzZkYzFkMWViODcxYmVkNDgwZWFmZjUifQ.eyJpc3MiOiJodHRwczovL2t1YmVhZG0tY2x1MS5obHNkZXYubG9jYWw6MzIwMDAiLCJzdWIiOiJDZ1poWW5KMVkyVVNFV3hrWVhCZmFHeHpaR1YyWDJ4dlkyRnMiLCJhdWQiOiJrdWJlYWRtLWNsdTEtbG9naW4tYXBwIiwiZXhwIjoxNTAzNjIyMzgyLCJpYXQiOjE1MDM1MzU5ODIsImF0X2hhc2giOiJiYnQteWhhMkJBckhkdzBRY1lieVNRIiwiZW1haWwiOiJhbmR5YnJ1Y2VuZXRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImdyb3VwcyI6WyJncnAua3ViZWFkbS1jbHUxLnVzZXJzIl0sIm5hbWUiOiJBbmRyZXcgQnJ1Y2UifQ.Kt7OrHoz-Smo0gop3aJ-IakE0-3OSjXD_fGpg6oLSyn20FG6aZQ4lO-UaSc_8lmLuIKVEV20_dTUrsrbzGDStExu-xfJube0Jy6WGqZqDo5K6j8Yz3HU5aycb5DXwQx97BucmFc42d2FOPht-ZFCpZd4xe0APw8uL_WcfNbYb62kGGMJarBP552SIMRgPdwZlA6yEfBvfdia5j5Pni6a4XOYECMHX-pff7Bgcu9D2esQOe3PTDGSw_bz97mMI9WKYMCB_VbyAuy90aPJJeLNyMg1QOSibAfR8v-CoHs6aIhKyeIQMbSkz4A7S0lJW3ATpUWJFqo72QosoGe9npFBIw<\/em>\r\n<\/code><\/pre>\n<p>That last item is the bearer token &#8211; this must be injected into every <code>kubectl<\/code> call so that the Kubernetes API Server can apply authorizations to the invoked query. Here&#8217;s an example of a denied query (my user does not have permissions to list all cluster namespaces):<\/p>\n<pre><code>\r\nMacBook-Pro:~ l.abruce$ <strong>sab-k8s.sh kubectl get namespace<\/strong>\r\n<em>Enter password (abruce):\r\nError from server (Forbidden): User \"https:\/\/kubeadm-clu1.hlsdev.local:32000#CgZhYnJ1Y2USEWxkYXBfaGxzZGV2X2xvY2Fs\" cannot list namespaces at the cluster scope. (get namespaces)<\/em>\r\n<\/code><\/pre>\n<p>But other things work fine:<\/p>\n<pre><code>\r\nMacBook-Pro:~ l.abruce$ <strong>sab-k8s.sh kubectl get pod<\/strong>\r\n<em>NAME                          READY     STATUS    RESTARTS   AGE\r\ngitlab-3787346051-0gk71       1\/1       Running   0          49d\r\npostgresql-3002604634-zt03b   1\/1       Running   0          49d\r\nredis-240693514-6pnzc         1\/1       Running   0          49d<\/em>\r\n<\/code><\/pre>\n<h3>Our <code>dex<\/code> Users and Kubernetes Permissions<\/h3>\n<p>Each authorized user on our Kubernetes cluster is assigned a unique namespace; the user is given &#8220;owner&#8221; permissions on that namespace. Our users by default have the most minimal privileges elsewhere in the cluster.<\/p>\n<p>We&#8217;ll cover roles and roleBindings below.<\/p>\n<h4>A Note: FreeIPA Users vs. Kubernetes API Server Users<\/h4>\n<p>As you review the <a href=\"https:\/\/github.com\/andybrucenet\/kube-dex-dashboard\">GitHub repo<\/a> code the keen-eyed will observe that in the <code>dex\/rbac\/sab-users.yaml<\/code> file we define some variables in this template to setup a new user. Specifically:<\/p>\n<ul>\n<li><code>&lt;%= kubeadm_dex_user %&gt;<\/code> &#8211; This is the name on the FreeIPA domain (such as &#8220;abruce&#8221; or &#8220;fbar&#8221;). In other words, it is in plaintext.<\/li>\n<li><code>&lt;%= @kubeadm_dex_login['issuer'] -%&gt;<\/code> &#8211; This is the URI to the <code>dex<\/code> &#8220;issuer&#8221; (see <a href=\"https:\/\/github.com\/coreos\/dex\/blob\/master\/Documentation\/kubernetes.md\">Using dex with Kubernetes<\/a>).<\/li>\n<li><code>&lt;%= scope.function_k8s_dex_uid([kubeadm_dex_user, @kubeadm_dex_login['connector-id']]) -%&gt;<\/code> &#8211; This is actually a <em>function<\/em> that translates the plaintext <code>kubeadm_dex_user<\/code> to a base64-encoded value.<\/li>\n<\/ul>\n<p>So what does that mean? It means that, internally to the Kubernetes API Server, a &#8220;user&#8221; is actually a reference to the provider and a provider-specific encoding of the user ID. For example: my FreeIPA user ID <code>abruce<\/code> actually becomes <code>https:\/\/kubeadm-clu1.hlsdev.local:32000#CmpicnVjZRIRbGRhcF9obHNkZXZfbG9jYWw=<\/code> when represented by <code>dex<\/code> to the Kubernetes API Server. That presented a problem to us because we use Puppet to create roleBindings dynamically, because we had to translate the plaintext <code>kubeadm_dex_user<\/code> <a href=\"https:\/\/docs.puppet.com\/hiera\/\">Puppet Hiera<\/a> variable to the fully-encoded value expected by Kubernetes API Server.<\/p>\n<p>For the sake of completeness, here is the <a href=\"https:\/\/docs.puppet.com\/puppet\/5.1\/lang_template_erb.html\">Puppet ERB<\/a> function that performs this encoding. (We do not provide more Puppet settings because that would be a non-trivial task &#8211; we have <em>lots<\/em> of Puppet manifests and functions as part of our Kubernetes auto-deployment.)<\/p>\n<pre><code>\r\n# \/etc\/puppet\/modules\/sab\/lib\/puppet\/parser\/functions\/k8s_dex_uid.rb\r\n#require \"base64\"\r\n\r\n# k8s_dex_uid.rb, ABr\r\n# Solve bogus problem of k8s user IDs under latest 2.4.1 dex\r\n\r\nmodule Puppet::Parser::Functions\r\n  newfunction(:k8s_dex_uid) do |args|\r\n    uid = args[0]\r\n    connector = args[1]\r\n\r\n    # create the encoding string\r\n    encoded_string = 0x0A.chr + uid.length.chr + uid + 0x12.chr + connector.length.chr + connector\r\n    result = Base64.strict_encode64(encoded_string)\r\n    while result[-1] == '=' do\r\n      result = result.chop\r\n    end\r\n\r\n    # function result\r\n    result\r\n  end\r\nend\r\n<\/code><\/pre>\n<p>You can find a <code>bash<\/code> version of the above in the GitHub repo under <code>scripts\/sab-k8s.sh<\/code> (look for the <code>sab-k8s-x-dex-username<\/code> function).<\/p>\n<h4>Another Note: FreeIPA Groups in roleBindings aren&#8217;t encoded. Why Not?<\/h4>\n<p>More keen-eyed developers will have noticed that our roleBindings that target a backing FreeIPA group name &#8211; are not encoded. Here&#8217;s an example from earlier in this article:<\/p>\n<pre><code>\r\n---\r\n# required binding to permit std users to access dashboard\r\nkind: RoleBinding\r\napiVersion: rbac.authorization.k8s.io\/v1beta1\r\nmetadata:\r\n  namespace: kube-system\r\n  name: sab-dashboard-role-binding\r\nsubjects:\r\n- kind: Group\r\n  name: \"grp.kubeadm-clu&lt;%= @kubeadm_clu -%&gt;.users\"\r\n<\/code><\/pre>\n<p>You are correct to be mystified; prior to <code>dex<\/code> version 2.4.1, users were written as plaintext (e.g. <code>abruce<\/code> as a user ID) rather than the base64-encoded value. In fact, <a href=\"https:\/\/github.com\/coreos\/dex\/issues\/942\">this dex GitHub issue<\/a> discusses the problem. And the fact that the same logic is not applied to group names is, well, confusing. But the short answer is that group claims are presented as-is (plaintext) while user IDs are encoded as we do above.<\/p>\n<p>Of course &#8211; all of this is subject to change as soon as we upgrade our <code>dex<\/code>. Because why not&#8230;<\/p>\n<h4>So <code>dex<\/code> works &#8211; what is the problem?<\/h4>\n<p>So the above digression shows that our RBAC implementation works at the Kubernetes API layer. The problem arises because the Kubernetes Dashboard doesn&#8217;t actually use the bearer token or authentication \/ authorization strategy. Instead, you create a single Dashboard instance, normally running as a system account, and then blithely tell your users: &#8220;Use <code>kubectl proxy<\/code> to access the Dashboard.&#8221; Because doing that &#8211; loses user isolation and privileges as your single Dashboard instance executes all Kubernetes API commands in its own service account&#8217;s context.<\/p>\n<h3>Brief Talk about Kubernetes Authorizations<\/h3>\n<p>We need to discuss <a href=\"https:\/\/kubernetes.io\/docs\/admin\/authorization\/\">Kubernetes Authorizations<\/a> because they are at the heart of our Dashboard solution. Kubernetes authorization consists of roles and roleBindings. Roles have one or more rules defining permitted API verbs, while roleBindings do exactly what they sound like &#8211; they bind an existing role to Kubernetes entities.<\/p>\n<h4>roles and clusterRoles<\/h4>\n<p>Here is a sample role we developed as part of the isolated Dashboard effort:<\/p>\n<pre><code>\r\n---\r\n# role to permit minimal, readonly access\r\nkind: Role\r\napiVersion: rbac.authorization.k8s.io\/v1beta1\r\nmetadata:\r\n  namespace: kube-system\r\n  name: sab-dashboard-role\r\nrules:\r\n- apiGroups: [\"\"]\r\n  resources: [\"services\/proxy\"]\r\n  verbs: [\"create\"]\r\n<\/code><\/pre>\n<p>This role is assigned to permit POST (&#8220;create&#8221;) commands to be executed against the proxy service within a given Kubernetes namespace.<\/p>\n<p>It is also possible to create cluster-wide roles &#8211; here&#8217;s another example:<\/p>\n<pre><code>\r\n---\r\n# cluster role required for initial dashboard get\r\nkind: ClusterRole\r\napiVersion: rbac.authorization.k8s.io\/v1beta1\r\nmetadata:\r\n  name: sab-dashboard-clusterrole\r\nrules:\r\n- nonResourceURLs: [\"*\"]\r\n  verbs: [\"get\"]\r\n<\/code><\/pre>\n<p>Cluster roles apply to any namespace in the cluster, so it&#8217;s important to keep them well understood before granting cluster-wide privileges.<\/p>\n<h4>roleBindings and clusterRoleBindings<\/h4>\n<p>Bindings are used to assign roles (and associated privileges) to Kubernetes entities. Here is an example of both we used to solve the Dashboard problem:<\/p>\n<pre><code>\r\n---\r\n# required binding to permit std users to access dashboard\r\nkind: RoleBinding\r\napiVersion: rbac.authorization.k8s.io\/v1beta1\r\nmetadata:\r\n  namespace: kube-system\r\n  name: sab-dashboard-role-binding\r\nsubjects:\r\n- kind: Group\r\n  name: \"grp.kubeadm-clu&lt;%= @kubeadm_clu -%&gt;.users\"\r\n  apiGroup: rbac.authorization.k8s.io\r\nroleRef:\r\n  kind: Role\r\n  name: sab-dashboard-role\r\n  apiGroup: rbac.authorization.k8s.io\r\n---\r\n# required cluster binding to permit std users to access dashboard\r\nkind: ClusterRoleBinding\r\napiVersion: rbac.authorization.k8s.io\/v1beta1\r\nmetadata:\r\n  namespace: kube-system\r\n  name: sab-dashboard-clusterrole-binding\r\nsubjects:\r\n- kind: Group\r\n  name: \"grp.kubeadm-clu&lt;%= @kubeadm_clu -%&gt;.users\"\r\n  apiGroup: rbac.authorization.k8s.io\r\nroleRef:\r\n  kind: ClusterRole\r\n  name: sab-dashboard-clusterrole\r\n  apiGroup: rbac.authorization.k8s.io\r\n<\/code><\/pre>\n<p><em>NB: The <code>&lt;%= @kubeadm_clu -%&gt;<\/code> is because we use <a href=\"https:\/\/docs.puppet.com\/puppet\/5.1\/lang_template_erb.html\">Puppet ERB<\/a> as part of our solution. It may not apply in your case.<\/em><\/p>\n<p><em>NB #2: The keen-eyed will notice triumphantly that we use a clusterRoleBinding and that we provide a <code>namespace<\/code> &#8211; which is stupid because a cluster-wide role&#8230;has no namespace. We put it in there because it made it read more easily to us during development, but feel free to remove it from your own implementation.<\/em><\/p>\n<p>Be sure to notice that we leverage <em>group memberships<\/em> in the solution. Basically, if a given FreeIPA user is a member of a given group, then that is enough to provide access to that user&#8217;s dashboard (as well as the cluster-wide role which permits a user to access the service endpoint defined in the <code>kube-system<\/code> namespace). You can use this same type of approach to setup your own security policies based on RBAC.<\/p>\n<h3>&#8220;Shadow&#8221; Accounts and Dashboard<\/h3>\n<p>Let&#8217;s tie the above together for a solution. Basically, we want to run not a single Dashboard &#8211; but multiple Dashboards, where each Dashboard runs as a &#8220;shadow&#8221; Kubernetes service account that has <em>the same privileges as the corresponding user<\/em>.<\/p>\n<p>Here&#8217;s an example of the service accounts setup on my test cluster:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get sa | grep sab-sa<\/strong>\r\n<em>sab-sa-abruce               1         55d\r\nsab-sa-acheamitru           1         55d\r\nsab-sa-rkolwitz             1         55d<\/em>\r\n<\/code><\/pre>\n<p>Each one of these accounts ties back to an actual FreeIPA user. (We use <code>sab-sa-<\/code> to prefix the service account name). Let&#8217;s take a look at the roles \/ roleBindings for my test account:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get roles | grep abruce<\/strong>\r\n<em>sab-dashboard-role-abruce                  55d<\/em>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get roleBindings | grep abruce<\/strong>\r\n<em>sab-dashboard-rolebinding-abruce       55d<\/em>\r\n<\/code><\/pre>\n<p>Let&#8217;s take a look at the role assigned to the &#8220;shadow&#8221; service account:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get roles sab-dashboard-role-abruce -o yaml<\/strong>\r\n<em>apiVersion: rbac.authorization.k8s.io\/v1beta1\r\nkind: Role\r\nmetadata:\r\n  creationTimestamp: 2017-06-29T14:51:41Z\r\n  name: sab-dashboard-role-abruce\r\n  namespace: kube-system\r\n  resourceVersion: \"842\"\r\n  selfLink: \/apis\/rbac.authorization.k8s.io\/v1beta1\/namespaces\/kube-system\/roles\/sab-dashboard-role-abruce\r\n  uid: 767a81b1-5cda-11e7-b99b-782bcb74dd3c\r\nrules:\r\n- apiGroups:\r\n  - \"\"\r\n  resourceNames:\r\n  - sab-dashboard-abruce\r\n  resources:\r\n  - services\/proxy\r\n  verbs:\r\n  - get\r\n  - list\r\n  - delete\r\n  - update<\/em>\r\n<\/code><\/pre>\n<p>The above permits the shadow service account to have required privileges in the <code>kube-system<\/code> namespace. We also assign ownership privileges to the user&#8217;s namespace:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=abruce get roleBindings | grep sa-abruce<\/strong>\r\n<em>sab-sa-abruce   55d<\/em>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=abruce get roleBindings sab-sa-abruce -o yaml<\/strong>\r\n<em>apiVersion: rbac.authorization.k8s.io\/v1beta1\r\nkind: RoleBinding\r\nmetadata:\r\n  creationTimestamp: 2017-06-29T14:51:41Z\r\n  name: sab-sa-abruce\r\n  namespace: abruce\r\n  resourceVersion: \"846\"\r\n  selfLink: \/apis\/rbac.authorization.k8s.io\/v1beta1\/namespaces\/abruce\/rolebindings\/sab-sa-abruce\r\n  uid: 768bae06-5cda-11e7-b99b-782bcb74dd3c\r\nroleRef:\r\n  apiGroup: rbac.authorization.k8s.io\r\n  kind: Role\r\n  name: sab-ns-owner\r\nsubjects:\r\n- kind: ServiceAccount\r\n  name: sab-sa-abruce\r\n  namespace: kube-system<\/em>\r\n<\/code><\/pre>\n<h3>The Dashboard Instances<\/h3>\n<p>Now that we have serviceAccounts, roles, and roleBindings (including at the cluster level) we can start creating Dashboard instances.<\/p>\n<p>The Dashboards themselves must run within the <code>kube-system<\/code> namespace, which is why we have the corresponding Kubernetes role for the service account in the <code>kube-system<\/code> namespaces.<\/p>\n<p>Here is a manifest for one of the Dashboards, defining the service and the deployment:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get deploy | grep dashboard-abruce<\/strong>\r\n<em>sab-dashboard-abruce       1         1         1            1           55d<\/em>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get deploy sab-dashboard-abruce -o yaml<\/strong>\r\n<em>apiVersion: extensions\/v1beta1\r\nkind: Deployment\r\nmetadata:\r\n  annotations:\r\n    deployment.kubernetes.io\/revision: \"1\"\r\n  creationTimestamp: 2017-06-29T14:51:46Z\r\n  generation: 1\r\n  labels:\r\n    k8s-app: sab-dashboard-abruce\r\n  name: sab-dashboard-abruce\r\n  namespace: kube-system\r\n  resourceVersion: \"1299\"\r\n  selfLink: \/apis\/extensions\/v1beta1\/namespaces\/kube-system\/deployments\/sab-dashboard-abruce\r\n  uid: 7964d221-5cda-11e7-b99b-782bcb74dd3c\r\nspec:\r\n  replicas: 1\r\n  revisionHistoryLimit: 10\r\n  selector:\r\n    matchLabels:\r\n      k8s-app: sab-dashboard-abruce\r\n  strategy:\r\n    rollingUpdate:\r\n      maxSurge: 1\r\n      maxUnavailable: 1\r\n    type: RollingUpdate\r\n  template:\r\n    metadata:\r\n      creationTimestamp: null\r\n      labels:\r\n        k8s-app: sab-dashboard-abruce\r\n    spec:\r\n      containers:\r\n      - image: gcr.io\/google_containers\/kubernetes-dashboard-amd64:v1.6.1\r\n        imagePullPolicy: IfNotPresent\r\n        livenessProbe:\r\n          failureThreshold: 3\r\n          httpGet:\r\n            path: \/\r\n            port: 9090\r\n            scheme: HTTP\r\n          initialDelaySeconds: 30\r\n          periodSeconds: 10\r\n          successThreshold: 1\r\n          timeoutSeconds: 30\r\n        name: sab-dashboard-abruce\r\n        ports:\r\n        - containerPort: 9090\r\n          protocol: TCP\r\n        resources: {}\r\n        terminationMessagePath: \/dev\/termination-log\r\n        terminationMessagePolicy: File\r\n      dnsPolicy: ClusterFirst\r\n      nodeSelector:\r\n        dedicated: master\r\n      restartPolicy: Always\r\n      schedulerName: default-scheduler\r\n      securityContext: {}\r\n      serviceAccount: sab-sa-abruce\r\n      serviceAccountName: sab-sa-abruce\r\n      terminationGracePeriodSeconds: 30\r\n      tolerations:\r\n      - effect: NoSchedule\r\n        key: node-role.kubernetes.io\/master\r\nstatus:\r\n  availableReplicas: 1\r\n  conditions:\r\n  - lastTransitionTime: 2017-06-29T14:51:46Z\r\n    lastUpdateTime: 2017-06-29T14:51:46Z\r\n    message: Deployment has minimum availability.\r\n    reason: MinimumReplicasAvailable\r\n    status: \"True\"\r\n    type: Available\r\n  observedGeneration: 1\r\n  readyReplicas: 1\r\n  replicas: 1\r\n  updatedReplicas: 1<\/em>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get service sab-dashboard-abruce -o yaml<\/strong>\r\n<em>apiVersion: v1\r\nkind: Service\r\nmetadata:\r\n  creationTimestamp: 2017-06-29T14:51:46Z\r\n  labels:\r\n    k8s-app: sab-dashboard-abruce\r\n  name: sab-dashboard-abruce\r\n  namespace: kube-system\r\n  resourceVersion: \"931\"\r\n  selfLink: \/api\/v1\/namespaces\/kube-system\/services\/sab-dashboard-abruce\r\n  uid: 797218b7-5cda-11e7-b99b-782bcb74dd3c\r\nspec:\r\n  clusterIP: 10.97.14.26\r\n  ports:\r\n  - port: 80\r\n    protocol: TCP\r\n    targetPort: 9090\r\n  selector:\r\n    k8s-app: sab-dashboard-abruce\r\n  sessionAffinity: None\r\n  type: ClusterIP\r\nstatus:\r\n  loadBalancer: {}<\/em>\r\n<\/code><\/pre>\n<p>And the final result:<\/p>\n<pre><code>\r\n[root@lpekbclux210 ~]# <strong>kubectl --namespace=kube-system get all | grep sab-dashboard-abruce<\/strong>\r\n<em>po\/sab-dashboard-abruce-2861302251-7thm3              1\/1       Running   2          55d\r\nsvc\/sab-dashboard-abruce       10.97.14.26      <none>        80\/TCP           55d\r\ndeploy\/sab-dashboard-abruce       1         1         1            1           55d\r\nrs\/sab-dashboard-abruce-2861302251      1         1         1         55d<\/em>\r\n<\/code><\/pre>\n<h3>Accessing the Dashboard<\/h3>\n<p>We started the article above with an example of calling our <code>sab-k8s.sh<\/code> shell script to login. That shell script also wraps access to <code>kubectl<\/code>, so we use it to run a local proxy:<\/p>\n<pre><code>\r\nsab-k8s.sh kubectl proxy\r\n<\/code><\/pre>\n<p>As with a &#8220;normal&#8221; <code>kubectl proxy<\/code>, this permits local forwarding to the Dashboard instance (this, in fact, is exactly the same way that one would normally access a Dashboard). However, because we run proxied instances of each Dashboard where each instance has specialized permissions only for a particular <code>dex<\/code> user, the URI used to access the Dashboard is different from the Kubernetes standard one.<\/p>\n<p>To access the Dashboard, we use:<\/p>\n<pre><code>\r\nhttp:\/\/localhost:8001\/api\/v1\/namespaces\/kube-system\/services\/sab-dashboard-abruce\/proxy\/\r\n<\/code><\/pre>\n<p>The <code>sab-dashboard-abruce<\/code> indicates to access the Dashboard service endpoint we defined above. The result? We get access to a dashboard&#8230;here&#8217;s a <code>curl<\/code> command to demonstrate it works:<\/p>\n<pre><code>\r\nMacBook-Pro:~ l.abruce$ <strong>curl http:\/\/localhost:8001\/api\/v1\/namespaces\/kube-system\/services\/sab-dashboard-abruce\/proxy\/<\/strong>\r\n<em> &lt;!doctype html&gt; &lt;html ng-app=\"kubernetesDashboard\"&gt; &lt;head&gt; &lt;meta charset=\"utf-8\"&gt; &lt;title ng-controller=\"kdTitle as $ctrl\" ng-bind=\"$ctrl.title()\"&gt;&lt;\/title&gt; &lt;link rel=\"icon\" type=\"image\/png\" href=\"assets\/images\/kubernetes-logo.png\"&gt; &lt;meta name=\"viewport\" content=\"width=device-width\"&gt; &lt;link rel=\"stylesheet\" href=\"static\/vendor.803608cb.css\"&gt; &lt;link rel=\"stylesheet\" href=\"static\/app.336a76b4.css\"&gt; &lt;\/head&gt; &lt;body&gt; &lt;!--[if lt IE 10]&gt;\r\n      &lt;p class=\"browsehappy\"&gt;You are using an &lt;strong&gt;outdated&lt;\/strong&gt; browser.\r\n      Please &lt;a href=\"http:\/\/browsehappy.com\/\"&gt;upgrade your browser&lt;\/a&gt; to improve your\r\n      experience.&lt;\/p&gt;\r\n    &lt;![endif]--&gt; &lt;kd-chrome layout=\"column\" layout-fill=\"\"&gt; &lt;\/kd-chrome&gt; &lt;script src=\"static\/vendor.31531c85.js\"&gt;&lt;\/script&gt; &lt;script src=\"api\/appConfig.json\"&gt;&lt;\/script&gt; &lt;script src=\"static\/app.f69f96ab.js\"&gt;&lt;\/script&gt; &lt;\/body&gt; &lt;\/html&gt;<\/em>\r\n<\/code><\/pre>\n<p>The fact that we got a response indicates that the Dashboard instance is up and running.<\/p>\n<h2>Still More Problems!<\/h2>\n<p>The biggest problems with this approach include:<\/p>\n<ul>\n<li><strong>The approach is kludgy<\/strong> &#8211; Duplicating user accounts with a &#8220;shadow&#8221; service account does not scale. In our case, we use automated shell scripts to detect new user accounts and &#8211; if the accounts are members of a particular FreeIPA group &#8211; we auto-create the corresponding shadow service account and provision the Dashboard.<\/li>\n<\/ul>\n<p>Despite the problems, the approach used by this article at least solves the problem of a single, monolithic Dashboard. And, future Kubernetes Dashboard deployments will no doubt address these shortcomings and obviate the need to have multiple Dashboard instances running.<\/p>\n<p>That is all.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hello All. Today we are going to look at Kubernetes Dashboard, Authentication, and Isolation. The Code Let&#8217;s put the code up front; that way, if you don&#8217;t want to bother with the article you can start by poking around on &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\"> <span class=\"screen-reader-text\">Kubernetes Dashboard, Authentication, Isolation<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[93,1],"tags":[94,71],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.2 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Kubernetes Dashboard, Authentication, Isolation - softwareab<\/title>\n<meta name=\"description\" content=\"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Kubernetes Dashboard, Authentication, Isolation - softwareab\" \/>\n<meta property=\"og:description\" content=\"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\" \/>\n<meta property=\"og:site_name\" content=\"softwareab\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/cloudraticsolutions\/\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/cloudraticsolutions\/\" \/>\n<meta property=\"article:published_time\" content=\"2017-08-24T02:25:43+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-08-30T21:35:02+00:00\" \/>\n<meta name=\"author\" content=\"Andrew Bruce\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@realcloudratics\" \/>\n<meta name=\"twitter:site\" content=\"@realcloudratics\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Andrew Bruce\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\"},\"author\":{\"name\":\"Andrew Bruce\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600\"},\"headline\":\"Kubernetes Dashboard, Authentication, Isolation\",\"datePublished\":\"2017-08-24T02:25:43+00:00\",\"dateModified\":\"2017-08-30T21:35:02+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\"},\"wordCount\":1809,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600\"},\"keywords\":[\"kubernetes\",\"virtualization\"],\"articleSection\":[\"Kubernetes\",\"Teknocratica\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\",\"url\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\",\"name\":\"Kubernetes Dashboard, Authentication, Isolation - softwareab\",\"isPartOf\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#website\"},\"datePublished\":\"2017-08-24T02:25:43+00:00\",\"dateModified\":\"2017-08-30T21:35:02+00:00\",\"description\":\"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.softwareab.net\/wordpress\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"kubernetes\",\"item\":\"https:\/\/www.softwareab.net\/wordpress\/tag\/kubernetes\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Kubernetes Dashboard, Authentication, Isolation\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#website\",\"url\":\"https:\/\/www.softwareab.net\/wordpress\/\",\"name\":\"softwareab\",\"description\":\"Technocratica, Technopolitik, Technophobia\",\"publisher\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.softwareab.net\/wordpress\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600\",\"name\":\"Andrew Bruce\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/www.softwareab.net\/wordpress\/wp-content\/uploads\/2024\/03\/andy-cartoon.jpg\",\"contentUrl\":\"https:\/\/www.softwareab.net\/wordpress\/wp-content\/uploads\/2024\/03\/andy-cartoon.jpg\",\"width\":400,\"height\":330,\"caption\":\"Andrew Bruce\"},\"logo\":{\"@id\":\"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/image\/\"},\"description\":\"Team-oriented systems mentor with deep knowledge of numerous software methodologies, technologies, languages, and operating systems. Excited about turning emerging technology into working production-ready systems. Focused on moving software teams to a higher level of world-class application development. Specialties:Software analysis and development...Product management through the entire lifecycle...Discrete product integration specialist!\",\"sameAs\":[\"http:\/\/cloudraticsolutions.net\/\",\"https:\/\/www.facebook.com\/cloudraticsolutions\/\",\"https:\/\/twitter.com\/realcloudratics\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Kubernetes Dashboard, Authentication, Isolation - softwareab","description":"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/","og_locale":"en_US","og_type":"article","og_title":"Kubernetes Dashboard, Authentication, Isolation - softwareab","og_description":"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.","og_url":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/","og_site_name":"softwareab","article_publisher":"https:\/\/www.facebook.com\/cloudraticsolutions\/","article_author":"https:\/\/www.facebook.com\/cloudraticsolutions\/","article_published_time":"2017-08-24T02:25:43+00:00","article_modified_time":"2017-08-30T21:35:02+00:00","author":"Andrew Bruce","twitter_card":"summary_large_image","twitter_creator":"@realcloudratics","twitter_site":"@realcloudratics","twitter_misc":{"Written by":"Andrew Bruce","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#article","isPartOf":{"@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/"},"author":{"name":"Andrew Bruce","@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600"},"headline":"Kubernetes Dashboard, Authentication, Isolation","datePublished":"2017-08-24T02:25:43+00:00","dateModified":"2017-08-30T21:35:02+00:00","mainEntityOfPage":{"@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/"},"wordCount":1809,"commentCount":0,"publisher":{"@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600"},"keywords":["kubernetes","virtualization"],"articleSection":["Kubernetes","Teknocratica"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/","url":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/","name":"Kubernetes Dashboard, Authentication, Isolation - softwareab","isPartOf":{"@id":"https:\/\/www.softwareab.net\/wordpress\/#website"},"datePublished":"2017-08-24T02:25:43+00:00","dateModified":"2017-08-30T21:35:02+00:00","description":"Workaround for the Kubernetes Dashboard single process problem and associated privilege escalation. Leverages native Kubernetes to solve.","breadcrumb":{"@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.softwareab.net\/wordpress\/kubernetes-dashboard-authentication-isolation\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.softwareab.net\/wordpress\/"},{"@type":"ListItem","position":2,"name":"kubernetes","item":"https:\/\/www.softwareab.net\/wordpress\/tag\/kubernetes\/"},{"@type":"ListItem","position":3,"name":"Kubernetes Dashboard, Authentication, Isolation"}]},{"@type":"WebSite","@id":"https:\/\/www.softwareab.net\/wordpress\/#website","url":"https:\/\/www.softwareab.net\/wordpress\/","name":"softwareab","description":"Technocratica, Technopolitik, Technophobia","publisher":{"@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.softwareab.net\/wordpress\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/1337443eaeb75104e0410b508e67f600","name":"Andrew Bruce","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/image\/","url":"https:\/\/www.softwareab.net\/wordpress\/wp-content\/uploads\/2024\/03\/andy-cartoon.jpg","contentUrl":"https:\/\/www.softwareab.net\/wordpress\/wp-content\/uploads\/2024\/03\/andy-cartoon.jpg","width":400,"height":330,"caption":"Andrew Bruce"},"logo":{"@id":"https:\/\/www.softwareab.net\/wordpress\/#\/schema\/person\/image\/"},"description":"Team-oriented systems mentor with deep knowledge of numerous software methodologies, technologies, languages, and operating systems. Excited about turning emerging technology into working production-ready systems. Focused on moving software teams to a higher level of world-class application development. Specialties:Software analysis and development...Product management through the entire lifecycle...Discrete product integration specialist!","sameAs":["http:\/\/cloudraticsolutions.net\/","https:\/\/www.facebook.com\/cloudraticsolutions\/","https:\/\/twitter.com\/realcloudratics"]}]}},"_links":{"self":[{"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/posts\/1042"}],"collection":[{"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/comments?post=1042"}],"version-history":[{"count":14,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/posts\/1042\/revisions"}],"predecessor-version":[{"id":1056,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/posts\/1042\/revisions\/1056"}],"wp:attachment":[{"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/media?parent=1042"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/categories?post=1042"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.softwareab.net\/wordpress\/wp-json\/wp\/v2\/tags?post=1042"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}