{"id":3387,"date":"2018-05-28T18:02:02","date_gmt":"2018-05-28T23:02:02","guid":{"rendered":"https:\/\/dev.iachieved.it\/iachievedit\/?p=3387"},"modified":"2018-05-28T18:02:02","modified_gmt":"2018-05-28T23:02:02","slug":"ansible-and-aws-part-5","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-5\/","title":{"rendered":"Ansible and AWS &#8211; Part 5"},"content":{"rendered":"<p>In Part 5 of our series, we&#8217;ll explore provisioning users and groups with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ansible_(software)\">Ansible<\/a> on our <a href=\"https:\/\/en.wikipedia.org\/wiki\/Amazon_Web_Services\">AWS<\/a> servers.<\/p>\n<p>Anyone who has had to add users to an operating environment knows how complex things can get in a hurry. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lightweight_Directory_Access_Protocol\">LDAP<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Active_Directory\">Active Directory<\/a>, and other technologies are designed to provide a centralized repository of users, groups, and access rules.  Or, for Linux systems, you can skip that complexity and provision users directly on the server.  If you have a lot of servers, Ansible can easily be used to add and delete users and provision access controls.<\/p>\n<p>Now, if you come from an &#8220;enterprise&#8221; background you might protest and assert that LDAP is <i>the only<\/i> way to manage users across your servers.  You&#8217;re certainly entitled to your opinion.  But if you&#8217;re managing a few dozen or so machines, there&#8217;s nothing wrong (in my book) with straight up Linux user provisioning.<\/p>\n<p>Regardless of the technology used, thought must still be given to how your users will be organized, and what permissions users will be given.  For example, you might have operations personnel that require <code>sudo<\/code> access on all servers.  Some of your developers may be given the title <code>architect<\/code> which provides them the luxury of <code>sudo<\/code> as well on certain servers.  Or, you might have a test group that is granted <code>sudo<\/code> access on test servers, but not on staging servers.  And so on.  The point is, neither LDAP, Active Directory, or Ansible negate your responsbility of giving thought to how users and groups are organized and setting a policy around it.<\/p>\n<p>So, let&#8217;s put together a team that we&#8217;ll give different privileges on different systems.  Our hypothetical team looks like this:<\/p>\n<table>\n<tr>\n<th>Name<\/th>\n<th>Group<\/th>\n<\/tr>\n<tr>\n<td>alice<\/td>\n<td>architects<\/td>\n<tr>\n<tr>\n<td>bob<\/td>\n<td>developers<\/td>\n<tr>\n<tr>\n<td>carol<\/td>\n<td>operations<\/td>\n<tr>\n<tr>\n<td>dave<\/td>\n<td>testers<\/td>\n<tr>\n<tr>\n<td>dave<\/td>\n<td>tptesters<\/td>\n<tr>\n<\/table>\n<p>We&#8217;ve decided that access on a given server (or environment) will follow these rules:<\/p>\n<table>\n<tr>\n<th>Environment<\/td>\n<th>Access<\/th>\n<\/tr>\n<tr>\n<td>production<\/td>\n<td>Only architects and operations gets access to the environment, and they get `sudo` access<\/td>\n<tr>\n<td>staging<\/td>\n<td>All users except tptesters get access to the environment, only architects, operations, and developers get `sudo` access<\/td>\n<\/tr>\n<tr>\n<td>test<\/td>\n<td>All users get access to the environent, and with the exception of `tptesters`, they get `sudo` access<\/td>\n<\/tr>\n<tr>\n<td>operations<\/td>\n<td>Only operations get access to their environment, and they get `sudo` access<\/td>\n<\/tr>\n<\/table>\n<p>Now, let&#8217;s look at how we can enforce these rules with Ansible!<\/p>\n<h2>users Role<\/h2>\n<p>We&#8217;re going to introduce Ansible <i>roles<\/i> in this post.  This is by no means a complete tutorial on roles, for that you might want to check out the <a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_reuse_roles.html\">Ansible documentation<\/a>.<\/p>\n<p>Note:  <code>git clone https:\/\/github.com\/iachievedit\/ansible-helloworld<\/code> to get the example repository for this series, and switch to <code>part4<\/code> (<code>git checkout part4<\/code>) to pick up where we left off.<\/p>\n<p>Let&#8217;s get started by creating our <code>roles<\/code> directory in <code>ansible-helloworld<\/code>.<\/p>\n<p>[code lang=text]<br \/>\n# git clone https:\/\/github.com\/iachievedit\/ansible-helloworld<br \/>\n# cd ansible-helloworld<br \/>\n# git checkout part4<br \/>\n# mkdir roles<br \/>\n[\/code]<\/p>\n<p>Now we&#8217;re going to use the command <code>ansible-galaxy<\/code> to create a template (not to be confused with a Jinja2 template!) for our first role.<\/p>\n<p>[code lang=text]<br \/>\n# cd roles<br \/>\n# ansible-galaxy init users<br \/>\n&#8211; users was created successfully<br \/>\n[\/code]<\/p>\n<p>Drop in to the <code>users<\/code> directory that was just created and you&#8217;ll see:<\/p>\n<p>[code lang=text]<br \/>\n# cd users<br \/>\n# ls<br \/>\nREADME.md files     meta      templates vars<br \/>\ndefaults  handlers  tasks     tests<br \/>\n[\/code]<\/p>\n<p>We&#8217;ll be working in three directories, <code>vars<\/code>, <code>files<\/code>, and <code>tasks<\/code>.  In <code>roles\/vars\/main.yml<\/code> add the following:<\/p>\n<pre class=\"lang:yaml\">\n---\nusergroups:\n  - architects\n  - developers\n  - operations\n  - testers\n  - tptesters\n\nusers:\n  - {name:  alice, group: architects}\n  - {name:  bob,   group: developers}\n  - {name:  carol, group: operations}\n  - {name:  dave,  group: testers}\n  - {name:  eve,   group: tptesters}\n<\/pre>\n<p>Recall in previous tutorials our variables definitions were simple tag-value pairs (like <code>HOSTNAME:  helloworld<\/code>).  In this post we&#8217;re going to take advantage of the fact that variables can be complex types that include lists and dictionaries.<\/p>\n<p>Now, let&#8217;s create our <code>users<\/code> role tasks.  We&#8217;ll start with creating our groups.  In <code>roles\/tasks\/main.yml<\/code>:<\/p>\n<pre class=\"lang:yaml\">\n---\n- name:  Create groups\n  group:\n    name:  \"{{ item }}\"\n    state: present\n  loop:  \"{{ usergroups }}\"\n<\/pre>\n<p>There&#8217;s another new Ansible keyword in use here, <code>loop<\/code>.  <code>loop<\/code> will take the items <i>in the list<\/i> given by <code>usergroups<\/code> and iterate over them, with each item being &#8220;plugged in&#8221; to <code>item<\/code>.  The Python equivalent might look like:<\/p>\n<pre class=\"lang:python\">\nfor item in usergroups:\n  groupadd(item) # Assume groupadd is implemented\n<\/pre>\n<p>Loops are powerful constructs in roles and playbooks, so make sure and review the <a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_loops.html\">Ansible documentation<\/a> to review what all can be accomplished with them.  Also check out Chris Torgalson&#8217;s <a href=\"https:\/\/chromatichq.com\/blog\/untangling-ansible-loops\">Untangling Ansible&#8217;s Loops<\/a>,  a great overview of Ansible loops and how to leverage them in your playbooks.  It also turns out this post is using loops and various constructs to provision users, so definitely compare and contrast the different approaches!<\/p>\n<p>Our next Ansible task will create the users <i>and<\/i> place them in the appropriate group.<\/p>\n<pre class=\"lang:yaml\">\n# Creating users\n- name:  Create users\n  user:\n    name:  \"{{ item.name }}\"\n    state: present\n    groups: \"{{ item.group }}\"\n  loop:  \"{{ users }}\"\n<\/pre>\n<p>Here it&#8217;s important to note that <code>users<\/code> is being looped over (that is, every item in the list <code>users<\/code>), and that we&#8217;re using a dot-notation to access values <i>inside<\/i> <code>item<\/code>.  For the first entry in the list, <code>item<\/code> would have:<\/p>\n<p>[code lang=text]<br \/>\nitem.name = alice<br \/>\nitem.group = architects<br \/>\n[\/code]<\/p>\n<p>Now, we could have chosen to allow for multiple groups for each user, in which case we might have defined something like:<\/p>\n<pre class=\"lang:yaml\">\nusers:\n  - {name:  alice, groups: [architects, developers]}\n<\/pre>\n<p>That looks pretty good so we&#8217;ll stick with that for the final product.<\/p>\n<p>With our user definitions in hand, let&#8217;s create an appropriate task to create them in the correct environment.  There are two more keywords to introduce:  <code>block<\/code> and <code>when<\/code>.  Let&#8217;s take a look:<\/p>\n<pre class=\"lang:yaml\">\n- block:\n  - name:  Create users for production servers\n    user:\n      name:  \"{{ item.name }}\"\n      state: present\n      groups: \"{{ item.groups }}\"\n    loop:  \"{{ users }}\"\n  when:\n    - \"'production' in group_names\"\n    - \"('architects' in item.groups) or\n       ('operations' in item.groups)\"\n<\/pre>\n<p>The <code>block<\/code> keyword allows us to group a set of tasks together, and is frequently used in conjunction with some type of &#8220;scoping&#8221; keyword.  In this example, we&#8217;re using the <code>when<\/code> keyword to execute the block when a certain condition is met.  The <code>tags<\/code> keyword is another &#8220;scoping&#8221; keyword that is useful with a <code>block<\/code>.<\/p>\n<p>Our <code>when<\/code> conditional indicates that the block will run only if the following conditions are met:<\/p>\n<ul>\n<li>the host is in the <code>production<\/code> group (as defined in <code>ansible_hosts<\/code>)<\/li>\n<li>the user is in either the <code>architects<\/code> or <code>operations<\/code> group<\/li>\n<\/ul>\n<p>The syntax for specifiying this logic looks a little contrived, but it&#8217;s quite simple and uses <code>in<\/code> to return true if a given value is in the specified list.  <code>'production' in group_names<\/code> is true if the <code>group_names<\/code> list contains the value <code>production<\/code>.  Likewise for <code>item.groups<\/code>, but in this case we use the <code>or<\/code> conditional to add the user to the server if their <code>groups<\/code> value contains either <code>architects<\/code> or <code>operations<\/code>.<\/p>\n<p>We&#8217;re not quite done!  We want our architects and operations groups to have sudo access on the production servers, so we add the following to our block:<\/p>\n<pre class=\"lang:yaml\">\n  - name: Allow 'operations' group to have passwordless sudo\n    lineinfile:\n      dest: \/etc\/sudoers\n      state: present\n      regexp: '^%operations'\n      line: '%operations ALL=(ALL) NOPASSWD: ALL'\n      validate: 'visudo -cf %s'\n  - name: Allow 'architects' group to have passwordless sudo\n    lineinfile:\n      dest: \/etc\/sudoers\n      state: present\n      regexp: '^%architects'\n      line: '%architects ALL=(ALL) NOPASSWD: ALL'\n      validate: 'visudo -cf %s'\n<\/pre>\n<p>Combining everything together, for <i>production<\/i> we have:<\/p>\n<pre class=\"lang:yaml\">\n# Create users for production servers\n- block:\n  - name:  Create users for production servers\n    user:\n      name:  \"{{ item.name }}\"\n      state: present\n      groups: \"{{ item.groups }}\"\n    loop:  \"{{ users }}\"\n  - name: Allow 'operations' group to have passwordless sudo\n    lineinfile:\n      dest: \/etc\/sudoers\n      state: present\n      regexp: '^%operations'\n      line: '%operations ALL=(ALL) NOPASSWD: ALL'\n      validate: 'visudo -cf %s'\n  - name: Allow 'architects' group to have passwordless sudo\n    lineinfile:\n      dest: \/etc\/sudoers\n      state: present\n      regexp: '^%architects'\n      line: '%architects ALL=(ALL) NOPASSWD: ALL'\n      validate: 'visudo -cf %s'\n  when:\n    - \"'production' in group_names\"\n    - \"('architects' in item.groups) or\n       ('operations' in item.groups)\"\n<\/pre>\n<h3>SSH Keys<\/h3>\n<p>Users on our servers will gain access through SSH keys.  To add them:<\/p>\n<pre class=\"lang:yaml\">\n  - name:  Create authorized_keys for production server users\n    authorized_key:\n      user:  \"{{ item.name }}\"\n      state: present\n      key: \"{{ lookup('file', '{{ ssh_keys\/item.name }}') }}\"\n    loop:  \"{{ users }}\"\n<\/pre>\n<p>Another new module!  <code>authorized_key<\/code> will edit the <code>~\/.ssh\/authorized_keys<\/code> file of the given user and add the key specified in the <code>key<\/code> parameter.  The <a href=\"http:\/\/docs.ansible.com\/ansible\/latest\/user_guide\/playbooks_lookups.html\"><code>lookup<\/code> function<\/a> will go and get the key contents from a file (the first argument) given in the location <code>{{ ssh_keys\/item.name }}<\/code>, which will expand to our user&#8217;s name.<\/p>\n<p>Note that the <code>lookup<\/code> function searches the <code>files<\/code> directory in our role.  That is, we have the following:<\/p>\n<p>[code lang=text]<br \/>\nroles\/files\/ssh_keys\/alice<br \/>\n                     bob<br \/>\n                     carol<br \/>\n                     dave<br \/>\n                     eve<br \/>\n[\/code]<\/p>\n<p>We do not encrypt public keys (if someone complains you didn&#8217;t encrypt their public key, slap them, it&#8217;ll make you feel better).<\/p>\n<h3>Shells<\/h3>\n<p>It was years into my career before I realized there was more to life than <a href=\"https:\/\/en.wikipedia.org\/wiki\/KornShell\">ksh<\/a>.  No joke, I didn&#8217;t realize there was anything but!  Today there are a variety of shells, <a href=\"https:\/\/www.gnu.org\/software\/bash\/\">bash<\/a>, <a href=\"http:\/\/zsh.sourceforge.net\">zsh<\/a> and <a href=\"https:\/\/fishshell.com\">fish<\/a> just to name a few.  I&#8217;ve also learned that an individual&#8217;s shell of choice is often as sacrosanct as their choice of editor.  So let&#8217;s add the ability to set the user&#8217;s shell of preference.<\/p>\n<p>First, we need to specify the list of shells we&#8217;re going to support.  In <code>roles\/users\/vars\/main.yml<\/code> we&#8217;ll add:<\/p>\n<pre class=\"lang:yaml\">\nshells:\n  - zsh\n  - fish\n  - tcsh\n  - ksh\n<\/pre>\n<p><code>bash<\/code> is already present on our Ubuntu system, so no need to explicitly add it.<\/p>\n<p>Now, in our role task, we add the following before any users are created.<\/p>\n<pre class=\"lang:yaml\">\n- name:  Install shells\n  apt:\n    name:  \"{{ item }}\"\n    state: present\n    update_cache: true\n  with_items:  \"{{ shells }}\"\n<\/pre>\n<p>This will ensure all of the shell options we given users are properly installed on the server.<\/p>\n<p>Back to <code>roles\/users\/vars\/main.yml<\/code>, let&#8217;s set the shells of our users:<\/p>\n<pre class=\"lang:yaml\">\nusers:\n  - {name:  alice, groups: [architects, developers], shell: \/usr\/bin\/fish}\n  - {name:  bob,   groups: [developers],             shell: \/usr\/bin\/tcsh}\n  - {name:  carol, groups: [operations],             shell: \/bin\/bash}\n  - {name:  dave,  groups: [testers],                shell: \/usr\/bin\/ksh}\n  - {name:  eve,   groups: [tptesters],              shell: \/usr\/bin\/zsh}\n<\/pre>\n<p>A different shell for everyone!<\/p>\n<p>Then, again in our role task, we update any addition of a user to include their shell:<\/p>\n<pre class=\"lang:yaml\">\n- name:  Create users for production servers\n    user:\n      name:  \"{{ item.name }}\"\n      state: present\n      groups: \"{{ item.groups }}\"\n      shell:  \"{{ item.shell }}\"\n    loop:  \"{{ users }}\"\n<\/pre>\n<p>Quite simple and elegant.<\/p>\n<p>Editor&#8217;s Prerogative:  Since this is my blog, and you&#8217;re reading it, I&#8217;ll give you my personal editor preference.  <a href=\"https:\/\/www.gnu.org\/software\/emacs\/\">Emacs<\/a> (or an editor with Emacs keybindings, like <a href=\"https:\/\/www.sublimetext.com\">Sublime Text<\/a>) for writing (prose or code), and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Vim_(text_editor)\">Vim<\/a> for editing configuration files.  No joke.<\/p>\n<h3>Staging<\/h3>\n<p>Our production environment had a simple rule:  only architects and operations are allowed to login, and both get sudo access.  Our staging environment is a bit more complicated, all users except <code>tptesters<\/code> get access to the environment, but only architects, operations, and developers get sudo access.  Moreover, we want to have a single <code>lineinfile<\/code> task and use <code>with_items<\/code> in it to add the appropriate lines.  Unfortunately this isn&#8217;t as easy as it sounds, as having <code>with_items<\/code> in the <code>lineinfile<\/code> task interferes with our <code>loop<\/code> tasks.  So, we create a separate task specifically for our <code>sudoers<\/code> updates, and in the end have:<\/p>\n<pre class=\"lang:yaml\">\n# Create users for staging servers\n- block:\n  - name:  Create users for staging servers\n    user:\n      name:  \"{{ item.name }}\"\n      state: present\n      groups: \"{{ item.groups }}\"\n      shell:  \"{{ item.shell }}\"\n    loop:  \"{{ users }}\"\n  - name:  Create authorized_keys for staging server users\n    authorized_key:\n      user:  \"{{ item.name }}\"\n      state: present\n      key: \"{{ lookup('file', 'ssh_keys\/{{ item.name }}') }}\"\n    loop:  \"{{ users }}\"\n  when:\n    - \"'staging' in group_names\"\n    - \"('architects' in item.groups) or\n       ('operations' in item.groups) or\n       ('developers' in item.groups) or\n       ('testers'    in item.groups)\"\n\n# Add sudo for appropriate staging users\n- name: Allow groups to have passwordless sudo\n  lineinfile:\n    dest: \/etc\/sudoers\n    state: present\n    regexp: \"{{ item.regexp }}\"\n    line: \"{{ item.line }}\"\n    validate: 'visudo -cf %s'\n  with_items:\n    - {regexp: \"^%architects\", line:  \"%architects ALL=(ALL) NOPASSWD: ALL\"}\n    - {regexp: \"^%operations\", line:  \"%operations ALL=(ALL) NOPASSWD: ALL\"}\n    - {regexp: \"^%developers\", line:  \"%developers ALL=(ALL) NOPASSWD: ALL\"}\n  when: \"'staging' in group_names\"\n<\/pre>\n<p>Again, note that we first use a block to create our users and authorized_keys updates for the staging group, <i>only<\/i> doing so for architects, operations, developers, and testers.  The second task adds the appropriate lines in the sudoers file.<\/p>\n<h3>Deleting Users (or Groups)<\/h3>\n<p>We have a way to add users; we&#8217;ll also need a way to remove them (my telecom background comes through when I say &#8220;deprovision&#8221;).<\/p>\n<p>In <code>roles\/users\/vars\/main.yml<\/code> we&#8217;ll add a new variable <code>deletedusers<\/code> which contains a list of user names that are to be removed from a server.  While we&#8217;re at it, let&#8217;s add a section from groups that we want to delete as well.<\/p>\n<pre class=\"lang:yaml\">\ndeletedusers:\n  - {name:  frank}\n\ndeletedgroups:\n  - {name:  developer}\n<\/pre>\n<p>We can then update our user task:<\/p>\n<pre class=\"lang:yaml\">\n# Delete users\n- name:  Delete users in deletedusers list\n  user:\n    name:  \"{{ item.name }}\"\n    state: absent\n  loop:  \"{{ deletedusers }}\"\n\n# Delete groups\n- name:  Delete groups in deletedgroups list\n  group:\n    name:  \"{{ item.name }}\"\n    state: absent\n  loop:  \"{{ deletedgroups }}\"\n<\/pre>\n<p>As with the <code>users<\/code>, we&#8217;ll loop over <code>deletedusers<\/code> and use the <code>absent<\/code> state to remove the user from the system.  Finally, any groups that require deletion can be done so as well with <code>state: absent<\/code> on the <code>group<\/code> task.<\/p>\n<p>One last note with the <code>user<\/code> task with Ansible; we&#8217;ve only scratched the surface of its capabilities.  There are a variety of parameters that can be set such as <code>expires<\/code>, <code>home<\/code>, and of particular interest, <code>remove<\/code>.  Specifying <code>remove: yes<\/code> will <i>delete<\/i> the user&#8217;s home directory (and files in it), along with their mail spool.  If you truly want to be sure and nuke the user from orbit, specify <code>remove: yes<\/code> in your <code>user<\/code> task for deletion.<\/p>\n<h2>Recap<\/h2>\n<p>If you go and look at the <code>part5<\/code> branch of the GitHub repository, you&#8217;ll see that we&#8217;ve heavily refactored the <code>main.yml<\/code> file to rely on include statements.  Like good code, a single playbook or Ansible task file shouldn&#8217;t be too incredibly long.  In the end, our <code>roles\/users\/tasks\/main.yml<\/code> looks like this:<\/p>\n<pre class=\"lang:yaml\">\n---\n- include_tasks:  groups_and_shells.yml\n- include_tasks:  production.yml\n- include_tasks:  staging.yml\n- include_tasks:  operations.yml\n- include_tasks:  test.yml\n- include_tasks:  deletes.yml\n<\/pre>\n<p>Hopefully this post has given you some thoughts on how to leverage Ansible for adding and deleting users on your servers.  In a future post we might look at how to do the same thing, but with using LDAP and Ansible together.<\/p>\n<h2>This Series<\/h2>\n<p>Each post in this series is building upon the last.  If you missed something, here are the previous posts.  We&#8217;ve also put everything on <a href=\"https:\/\/github.com\/iachievedit\/ansible-helloworld\">this Github repository<\/a> with branches that contain all of the changes from one part to the next.<\/p>\n<ul>\n<li><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-1\/\">Part 1<\/a>\n<li><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-2\/\">Part 2<\/a>\n<li><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-3\/\">Part 3<\/a>\n<li><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/ansible-and-aws-part-4\/\">Part 4<\/a>\n<\/ul>\n<p>To get the most out of walking through this tutorial on your own, download the repository and check out <code>part4<\/code> to build your way through to <code>part5<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In Part 5 of our series, we&#8217;ll explore provisioning users and groups with Ansible on our AWS servers. Anyone who has had to add users to an operating environment knows how complex things can get in a hurry. LDAP, Active Directory, and other technologies are designed to provide a centralized repository of users, groups, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3318,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[69],"class_list":["post-3387","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-ansible-users-groups"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3387"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=3387"}],"version-history":[{"count":19,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3387\/revisions"}],"predecessor-version":[{"id":3427,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/3387\/revisions\/3427"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media\/3318"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=3387"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=3387"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=3387"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}