<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Data Unbound</title>
    <description>The latest articles on DEV Community by Data Unbound (@data_unbound).</description>
    <link>https://dev.to/data_unbound</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F415806%2F09afee6b-e0dd-4186-bcfc-9b699940da75.jpg</url>
      <title>DEV Community: Data Unbound</title>
      <link>https://dev.to/data_unbound</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/data_unbound"/>
    <language>en</language>
    <item>
      <title>How I Killed Bad Meetings</title>
      <dc:creator>Data Unbound</dc:creator>
      <pubDate>Mon, 06 Jul 2020 09:21:48 +0000</pubDate>
      <link>https://dev.to/data_unbound/how-i-killed-bad-meetings-3e16</link>
      <guid>https://dev.to/data_unbound/how-i-killed-bad-meetings-3e16</guid>
      <description>&lt;p&gt;&lt;a href="https://www.dataunbound.co.uk/how-i-killed-bad-meetings/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--88FD6AxK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.dataunbound.co.uk/wp-content/uploads/2020/07/people-2557396_1920.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  10 tips for making meetings better
&lt;/h4&gt;

&lt;p&gt;I realized we had a major problem. A colleague, distraught, pulled me aside. She’d had a bad meeting — one of the ones where your &lt;em&gt;soul&lt;/em&gt; ebbs away while the clock laughs at you. I’d heard it all before, that day, from four other people. The worst part?&lt;/p&gt;

&lt;p&gt;They weren’t the same meeting. It wasn’t just one soul-crusher; it was a culture of bad meetings. The chief complaint?&lt;/p&gt;

&lt;p&gt;These meetings were a waste of &lt;em&gt;everyone’s&lt;/em&gt; time.&lt;/p&gt;

&lt;p&gt;It was just before Christmas. So I purchased &lt;strong&gt;six&lt;/strong&gt; books on meetings and read them cover to cover. Of the six, I’d recommend two: &lt;strong&gt;&lt;a href="https://rosenfeldmedia.com/books/meeting-design/"&gt;Meeting Design&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.goodreads.com/book/show/35605373-kill-bad-meetings"&gt;Kill Bad Meetings&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the new year started, we did things differently.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #1: Don’t Have a Meeting
&lt;/h3&gt;

&lt;p&gt;We’ve all heard the complaint about the meeting that could have been an email. You see, meetings are a powerful tool to solve complex problems, but they’re expensive. They eat man-hours. Emails, however, are comparably cheap — written at a time convenient to the writer, read at a time convenient to the reader.&lt;/p&gt;

&lt;p&gt;Meetings should &lt;strong&gt;never&lt;/strong&gt; be the default.&lt;/p&gt;

&lt;p&gt;So when do you need a meeting? Only when you need &lt;em&gt;“&lt;/em&gt; &lt;strong&gt;&lt;em&gt;spaghetti&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6XAIMsEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2Ay_k8W7YOEwPt_NLf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6XAIMsEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2Ay_k8W7YOEwPt_NLf" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@byrdman85?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;DeMorris Byrd&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We call work that requires synchronous communication, discussion, co-creation, and active participation “spaghetti” because people must work in a highly interconnected way to achieve a collaborative goal.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;— Kevan Hall and Alan Hall, &lt;a href="https://www.goodreads.com/book/show/35605373-kill-bad-meetings"&gt;Kill Bad Meetings&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The book, &lt;strong&gt;&lt;a href="https://www.goodreads.com/book/show/35605373-kill-bad-meetings"&gt;Kill Bad Meetings&lt;/a&gt;&lt;/strong&gt;, goes on to specify some typical examples of &lt;em&gt;spaghetti&lt;/em&gt; interactions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discussions to make a decision&lt;/li&gt;
&lt;li&gt;Multidisciplinary problem-solving&lt;/li&gt;
&lt;li&gt;Co-creating new ideas and proposals live in the meeting&lt;/li&gt;
&lt;li&gt;Solving common problems&lt;/li&gt;
&lt;li&gt;Learning common skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That doesn’t mean everything else is an email. But if you can map the interactions &lt;strong&gt;clearly&lt;/strong&gt; , you don’t need a meeting. That may not be obvious. So let’s look at two common meeting &lt;strong&gt;anti-patterns&lt;/strong&gt;.&lt;/p&gt;




&lt;h4&gt;
  
  
  The Broadcast
&lt;/h4&gt;

&lt;p&gt;The meeting organizer has called you all together. He disseminates information for an hour. You’re only here to listen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--382CAd_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2ArGmKfUxb-Bu3AyYm" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--382CAd_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2ArGmKfUxb-Bu3AyYm" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@apellaes?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;Alexandre Pellaes&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Perhaps he just loves the sound of his voice or worries that if he sends an email, you won’t read it. And you probably won’t. At least you won’t if you fail to understand &lt;em&gt;why&lt;/em&gt; the information is important. But right behind complaints of too many are complaints of too many emails.&lt;/p&gt;

&lt;p&gt;We live in the information age. To not drown in the flood of information, we filter it out. As a survivor, you’d filter out this would-be email. Either way, you’re not listening unless the speaker is particularly entertaining or addressing your immediate concerns.&lt;/p&gt;

&lt;p&gt;Just as you are accountable for listening and reading, the speaker or writer must be discerning in what he sends out. If all output from a given channel is relevant and terse, you listen.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Broadcasts aren’t always bad&lt;/strong&gt;. Sometimes you should gather people to celebrate good news. Equally, some particularly thorny issues are best discussed to the group face-to-face.&lt;/p&gt;

&lt;p&gt;But if the information invokes &lt;em&gt;little-to-no&lt;/em&gt; emotion, &lt;strong&gt;send an email&lt;/strong&gt;.&lt;/p&gt;




&lt;h4&gt;
  
  
  The Check-in
&lt;/h4&gt;

&lt;p&gt;Communication flows from many to one in the more demeaning “check-in”. The organizer, usually the boss, summons his underlings to give &lt;em&gt;him&lt;/em&gt; information.&lt;/p&gt;

&lt;p&gt;Be better. Turn a regular check-in meeting into a regular email. Alternatively, schedule a 1–1 meeting with each of the reporters. Let’s look at the &lt;strong&gt;math&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Suppose&lt;/strong&gt; you have 1 manager with 6 reporters:&lt;/p&gt;

&lt;p&gt;1 X &lt;strong&gt;60-minute&lt;/strong&gt; meeting, &lt;strong&gt;1 manager and 6 reporters →7&lt;/strong&gt; person-hours&lt;/p&gt;

&lt;p&gt;6 X &lt;strong&gt;10-minute&lt;/strong&gt; meetings, &lt;strong&gt;1 manager and 6 reporters →&lt;/strong&gt; 2 person-hours&lt;/p&gt;

&lt;p&gt;It’s the same amount of the manger’s time but saves &lt;strong&gt;5 hours&lt;/strong&gt; of everyone else’s time.That’s &lt;em&gt;why&lt;/em&gt; these meetings feel so bad — they imply, “my time matters, but not yours”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;. 10 minutes won’t always be enough time. If an issue demands more time, request it, or deal with it separately. Also, some information will be useful to everyone. Distribute it later in an email.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #2: Have a Shorter Meeting
&lt;/h3&gt;

&lt;p&gt;But what if there is &lt;em&gt;some&lt;/em&gt; spaghetti in the meeting? Cut the non-spaghetti elements out of a meeting and move them into an email, or some other form of asynchronous communication. This might occur before, or after, the meeting as a preparation (e.g. read and know this, come with your best three ideas, or fill out this survey). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ftLgVKDB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2AAxHcsfgTVUf9okDf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ftLgVKDB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1000/0%2AAxHcsfgTVUf9okDf" alt=""&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@icons8?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;Icons8 Team&lt;/a&gt; on &lt;a href="https://unsplash.com?utm_source=medium&amp;amp;utm_medium=referral" rel="noreferrer noopener"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A clever meeting design can achieve this within the meeting for a fraction of the time. Take our previous “check-in” example, suppose there &lt;em&gt;was&lt;/em&gt; spaghetti, and the information reported &lt;em&gt;is&lt;/em&gt; of interest to the group.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call a &lt;strong&gt;20-minute&lt;/strong&gt; meeting.&lt;/li&gt;
&lt;li&gt;Have each reporter write down each headline on separate index cards (or Trello cards for remote meetings). That’s one headline per card. Explain that they have only 10 minutes.&lt;/li&gt;
&lt;li&gt;Collect the cards as they are completed. Curate them as you go, organizing them into themes and filtering out anything private or unconstructive.&lt;/li&gt;
&lt;li&gt;At the end of that 10 minutes, invite a brief discussion of the most interesting ones.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Looking at the math&lt;/strong&gt; :&lt;/p&gt;

&lt;p&gt;1 x &lt;strong&gt;20-minute&lt;/strong&gt; meeting, &lt;strong&gt;1 manager and 6 reporters&lt;/strong&gt; →1.66 person-hours&lt;/p&gt;

&lt;p&gt;That’s a &lt;strong&gt;third&lt;/strong&gt; of the original meeting and arguably better than an email. How so? Imagine the resulting email chain and the time that would go into it. An honest, open 20-minute meeting saves potentially hours of crafting gaurded, diplomatic emails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #3: Have Fewer People in a Meeting
&lt;/h3&gt;

&lt;p&gt;To save person-hours can cut hours or people. Culturally, this can be challenging.&lt;/p&gt;

&lt;p&gt;The first cultural pitfall is meeting invitations or participation being seen as conferring honor and not inviting someone or “asking them NOT to come” as a slight. Let’s flip on its head.&lt;/p&gt;

&lt;p&gt;Asking someone to come to a meeting when they don’t &lt;em&gt;need&lt;/em&gt; to be there is a slight. It says, “I don’t value your time”. Instead, be thoughtful and ask, “Is this really the best way they could be spending their time?”&lt;/p&gt;

&lt;p&gt;Besides, a meeting can only have so many active participants. Otherwise, it’s more like a dinner party. Perhaps that chaotic meeting (that should have been an email) should have been a dinner party? Regardless, if people aren’t active, they’re not part of the spaghetti. We need to minimize this.&lt;/p&gt;

&lt;p&gt;Ask which invitees provide only marginal value. If you have one or two people from a team in the meeting, you probably get 80% of the value of having the whole team at a fraction of the cost. Afterward, these ambassadors will convey pertinent information to their team better than the meeting would have.&lt;/p&gt;

&lt;p&gt;As a rule of thumb, I limit the meetings to 8 people (maybe 10 at a stretch) or break them up into groups for activities. Consider this a design constraint and use these tips. Keep in mind, your meeting goals in mind and consider &lt;em&gt;how&lt;/em&gt; each person adds something unique to the discussion/activities.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #4: Have an Explicit Goal
&lt;/h3&gt;

&lt;p&gt;I keep using the word “design”, but what does that mean? Design means making deliberate choices and planning to some &lt;strong&gt;end,&lt;/strong&gt; or goal.&lt;/p&gt;

&lt;p&gt;Ever been in a meeting and wonder what the purpose of it is? If there was one, do you think you achieved it? Was it a valuable use of your time? Of course not! By stating a goal upfront, you get everyone on the same page and foster participation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #5: Produce Something
&lt;/h3&gt;

&lt;p&gt;If you have a goal, how will you know if you achieved it? You should produce &lt;em&gt;something&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Perhaps the goal is to make an informed decision. Well, then you should &lt;em&gt;produce&lt;/em&gt; that decision — and the reasoning behind it. &lt;strong&gt;Write. It. Down.&lt;/strong&gt; That sounds elementary, but not writing down decisions is commonplace in a &lt;strong&gt;bad meeting culture&lt;/strong&gt;. In such places, colleagues often disagree on what choices were made — making it difficult to proceed as a team.&lt;/p&gt;

&lt;p&gt;Perhaps the goal is to generate ideas, a brainstorm. Great, you should produce a list of ideas. You will need to curate them in some way. I like distributing stickers to allow attendees to vote for the ideas they feel would be most impactful or achievable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #6: Have an Agenda
&lt;/h3&gt;

&lt;p&gt;And broadcast it beforehand. By agenda, I mean some written plan for the meeting. A good agenda explains how everyone’s time will achieve explicit goals. Knowing what is to be produced, why, and roughly how allows attendees to better prepare for the task.&lt;/p&gt;

&lt;p&gt;Also, sending the agenda well in advance allows invitees to make a more informed decision regarding attendance. Perhaps they can think of someone else better-suited to attend. Ever attended a meeting that fell apart because the expert was, in fact, the wrong person?&lt;/p&gt;

&lt;p&gt;Getting your team more involved in this self-selection improves meeting quality. However, the best results require they feel comfortable declining or suggesting a substitution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tip #7: Appoint a Facilitator
&lt;/h3&gt;

&lt;p&gt;In a good meeting, goals are met, everyone fulfills their role, everyone has a voice, and the interpersonal dynamics are healthy. A facilitator owns these meta-goals rather than participating directly in the meeting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A meeting facilitator:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shepherds&lt;/strong&gt; the participants back to the agenda, when needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entreats&lt;/strong&gt; the timid, but utterly pregnant with thought, to speak up&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dissuades&lt;/strong&gt; the more vocal participants from bloviating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits&lt;/strong&gt; unconstructive comments or behaviors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s not uncommon for the organizer to facilitate, but the facilitator should be someone impartial. More practically, it may be difficult to manage both roles all the time. Consider delegating this as an “introductory leadership opportunity”.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #8: Appoint a Scribe
&lt;/h3&gt;

&lt;p&gt;Many of these tips require that you send information about the meeting to interested parties afterward. Who writes it all down? Consider appointing a scribe.&lt;/p&gt;

&lt;p&gt;As the organizer, by default, this is you. Why not unburden yourself to better run the meeting? I often do this simply because some colleagues take much better notes.&lt;/p&gt;

&lt;p&gt;But be warned, many people don’t like scribing or find it hard to take notes &lt;em&gt;and&lt;/em&gt; be part of the discussion. While some like being the influence of putting what happened into words; others see it as demeaning. &lt;strong&gt;Don’t&lt;/strong&gt; press anyone into scribing; invite volunteers. Take turns.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #9: Follow Up
&lt;/h3&gt;

&lt;p&gt;Send out those notes. I generally commit everything to the company wiki and send an email with a link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I also take the opportunity to:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Thank everyone for their hard work&lt;/li&gt;
&lt;li&gt;Elicit feedback. What went well? What could we do better?&lt;/li&gt;
&lt;li&gt;Show attendees what they produced&lt;/li&gt;
&lt;li&gt;Celebrate the achievement of our goal&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should be able to prove that it was time well spent.&lt;/p&gt;

&lt;p&gt;Also, once people see that you regularly follow up, they’ll be more comfortable with &lt;em&gt;not&lt;/em&gt; attending.&lt;/p&gt;




&lt;h3&gt;
  
  
  Tip #10: Train People
&lt;/h3&gt;

&lt;p&gt;Lastly, if I have to sit through an hour fire safety training — preparing for a disaster that never happens, then it’s worth spending an hour training for a disaster that happens &lt;strong&gt;several times&lt;/strong&gt; a day. Train people to organize better meetings. Train people to be better attendees.&lt;/p&gt;




&lt;p&gt;The post &lt;a href="https://www.dataunbound.co.uk/how-i-killed-bad-meetings/"&gt;How I Killed Bad Meetings&lt;/a&gt; appeared first on &lt;a href="https://www.dataunbound.co.uk"&gt;Data Unbound&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related posts:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/data_unbound/agile-is-more-than-just-a-software-thing-1i4n-temp-slug-9415936"&gt;Agile is More Than Just a Software Thing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/data_unbound/listen-like-you-re-not-a-serial-killer-277g-temp-slug-5833051"&gt;Listen Like You’re Not a Serial Killer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/data_unbound/fixed-price-contracts-better-for-everyone-nb1-temp-slug-4237635"&gt;Fixed Price Contracts: Better for Everyone?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>business</category>
      <category>leadership</category>
      <category>meetings</category>
    </item>
    <item>
      <title>Terraform in Anger Part 1: AWS S3 Access</title>
      <dc:creator>Data Unbound</dc:creator>
      <pubDate>Tue, 05 May 2020 11:11:11 +0000</pubDate>
      <link>https://dev.to/data_unbound/terraform-in-anger-part-1-aws-s3-access-2fel</link>
      <guid>https://dev.to/data_unbound/terraform-in-anger-part-1-aws-s3-access-2fel</guid>
      <description>&lt;p&gt;&lt;a href="https://www.dataunbound.co.uk/terraform-in-anger-part-1-aws-s3-access/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D6xCEpq2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.dataunbound.co.uk/wp-content/uploads/2020/05/in-anger-terraform-aws-930x620.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform allows you to set up infrastructure across a wide variety of vendors and platforms. I love it because it’s declarative—meaning you just say what it is your want and Terraform figures out how to get there. There are many good introductions to Terraform out there, but they lack that real-word-project feel. What’s it like to use Terraform in anger? I want to explore that with a real project example built from the ground up.&lt;/p&gt;

&lt;p&gt;To that end, one need I frequently come across in &lt;a href="https://www.dataunbound.co.uk/"&gt;my freelance data science work&lt;/a&gt; is secure data transfer. Often clients need to send sensitive, possibly medical or top-secret, data. In this series, we’ll deliver a really slick experience for &lt;em&gt;three&lt;/em&gt; different types of users on AWS. With the first being secure upload to S3 via the &lt;strong&gt;AWS CLI&lt;/strong&gt;. Later installments will look at giving the client’s Business and IT users access via &lt;strong&gt;AWS Console (browser)&lt;/strong&gt; and &lt;strong&gt;SFTP&lt;/strong&gt;, respectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining The Problem
&lt;/h2&gt;

&lt;p&gt;Clearly we &lt;em&gt;can’t&lt;/em&gt; just create a public bucket because this data is sensitive. Equally, we &lt;em&gt;can’t&lt;/em&gt; give the client access to our entire platform. Though challenging, locking the client down to a secure bucket without access to anything else is the only feasible option for a real project. And we’ll need to do this while delivering pleasant user experience.&lt;/p&gt;

&lt;p&gt;Although we should create our own browser portal for data transfer, it's a lot of work. It would be fine for me to invest that time for my own business, but I’d think it negligent charging a client for something so bespoke unless core to their business. I am about delivering &lt;em&gt;value&lt;/em&gt; for money, &lt;em&gt;not&lt;/em&gt; building cathedrals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.freestuff.dataunbound.co.uk/nvdwpy7wjb"&gt;Download The Complete Series Example Code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Plan
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ET--OASI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.dataunbound.co.uk/wp-content/uploads/2020/05/terraform-x-aws-1-e1588759809512.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ET--OASI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.dataunbound.co.uk/wp-content/uploads/2020/05/terraform-x-aws-1-e1588759809512.png" alt="terraform and aws"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we’ll create a bucket that doesn’t expose any client information&lt;/li&gt;
&lt;li&gt;Then we’ll create a user for the client that has access to the AWS via an Access key&lt;/li&gt;
&lt;li&gt;After that, we need to define a policy that limits a user to only accessing S3 and only that bucket&lt;/li&gt;
&lt;li&gt;And finally, we’ll associate that policy to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, this is still a toy example because, on a real project, you would probably need to support several clients—each with many users. We’ll address that in part II of this tutorial, but keep it simple for now. Later we can refactor using some more advanced features of Terraform to enable multiple users. This may seem forced, but I always recommend programming iteratively: starting with something simple that works and building out the functionality through refactoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Terraform
&lt;/h2&gt;

&lt;p&gt;Being written in Go, Terraform, consequently, installs easily. Simply fetch it from &lt;a href="https://www.terraform.io/downloads.html"&gt;downloads&lt;/a&gt; (for your system), unzip it, and move it to a directory included in your system’s &lt;code&gt;PATH&lt;/code&gt;. Finally, check the version you’re are using. Here’s mine for reference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dataunbound&lt;span class="nv"&gt;$ &lt;/span&gt;terraform &lt;span class="nt"&gt;--versionTerraform&lt;/span&gt; v0.12.24
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;From here on, I’m assuming you have an AWS account, an access key, and &lt;code&gt;awscli&lt;/code&gt; working on your machine. So if you haven’t done that, you should do it now.&lt;/p&gt;
&lt;h2&gt;
  
  
  Terraform HELLO WORLD
&lt;/h2&gt;

&lt;p&gt;To kick things off, we’ll define AWS as a provider and nothing more.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This block states that we’ll be using &lt;a href="https://www.terraform.io/docs/providers/aws/index.html"&gt;AWS&lt;/a&gt; and want &lt;code&gt;eu-west-2&lt;/code&gt; to be our default region; &lt;code&gt;provider&lt;/code&gt; is a keyword. Nothing could be simpler.&lt;/p&gt;

&lt;p&gt;Now let’s “apply”. What could go wrong?!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dataunbound&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply

Error: Could not satisfy plugin requirements


Plugin reinitialization required. Please run &lt;span class="s2"&gt;"terraform init"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Plugins are external binaries that Terraform uses to access and manipulate
resources. The configuration provided requires plugins which can&lt;span class="se"&gt;\'&lt;/span&gt;t be located,
don&lt;span class="se"&gt;\'&lt;/span&gt;t satisfy the version constraints, or are otherwise incompatible.

Terraform automatically discovers provider requirements from your
configuration, including providers used &lt;span class="k"&gt;in &lt;/span&gt;child modules. To see the
requirements and constraints from each module, run &lt;span class="s2"&gt;"terraform providers"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Error: provider.aws: no suitable version installed
  version requirements: &lt;span class="s2"&gt;"(any version)"&lt;/span&gt;
  versions installed: none
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Fantastic! An asinine message that contains its own solution—i.e. initialize the project with &lt;code&gt;terraform init&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;Luckily, I use &lt;a href="https://github.com/nvbn/thefuck"&gt;thefuck&lt;/a&gt; and so should you. It corrects simple mistakes from the previous console command—unnecessary, but fun. Have a look:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dataunbound&lt;span class="nv"&gt;$ &lt;/span&gt;fuck
terraform init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; terraform apply &lt;span class="o"&gt;[&lt;/span&gt;enter/↑/↓/ctrl+c]

Initializing the backend...

Initializing provider plugins...
- Checking &lt;span class="k"&gt;for &lt;/span&gt;available provider plugins...
- Downloading plugin &lt;span class="k"&gt;for &lt;/span&gt;provider &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;hashicorp/aws&lt;span class="o"&gt;)&lt;/span&gt; 2.60.0...

The following providers &lt;span class="k"&gt;do &lt;/span&gt;not have any version constraints &lt;span class="k"&gt;in &lt;/span&gt;configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"..."&lt;/span&gt; constraints to the
corresponding provider blocks &lt;span class="k"&gt;in &lt;/span&gt;configuration, with the constraint strings
suggested below.

&lt;span class="k"&gt;*&lt;/span&gt; provider.aws: version &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.60"&lt;/span&gt;

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running &lt;span class="s2"&gt;"terraform plan"&lt;/span&gt; to see
any changes that are required &lt;span class="k"&gt;for &lt;/span&gt;your infrastructure. All Terraform commands
should now work.

If you ever &lt;span class="nb"&gt;set &lt;/span&gt;or change modules or backend configuration &lt;span class="k"&gt;for &lt;/span&gt;Terraform,
rerun this &lt;span class="nb"&gt;command &lt;/span&gt;to reinitialize your working directory. If you forget, other
commands will detect it and remind you to &lt;span class="k"&gt;do &lt;/span&gt;so &lt;span class="k"&gt;if &lt;/span&gt;necessary.

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 0 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So what happened here? Well, first &lt;code&gt;thefuck&lt;/code&gt; ran &lt;code&gt;terraform init&lt;/code&gt; and discovered we’re using AWS as a provider. Consequently, it installed the necessary dependencies (to our local Terraform). Then &lt;code&gt;terraform apply&lt;/code&gt; looked for what need to be done and found nothing, hence:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt;Resources: 0 added, 0 changed, 0 destroyed&lt;span class="k"&gt;**&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That’s because we have declared any resources. Let’s change that by declaring a bucket for our client’s data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Secure Bucket in S3
&lt;/h2&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This is our first &lt;a href="https://www.terraform.io/docs/configuration/resources.html"&gt;resource&lt;/a&gt;, &lt;code&gt;aws_s3_bucket&lt;/code&gt;, and we named it &lt;code&gt;test_client_bucket&lt;/code&gt;. When we apply, Terraform creates a private s3 bucket name “test-client-bucket-x130099”.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dataunbound&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_s3_bucket.test_client_bucket will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"test_client_bucket"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + acceleration_status         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + acl                         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"private"&lt;/span&gt;
      + arn                         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + bucket                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test-client-bucket-x130099"&lt;/span&gt;
      + bucket_domain_name          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + bucket_regional_domain_name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + force_destroy               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      + hosted_zone_id              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + region                      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-2"&lt;/span&gt;
      + request_payer               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + website_domain              &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + website_endpoint            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;

      + versioning &lt;span class="o"&gt;{&lt;/span&gt;
          + enabled    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
          + mfa_delete &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 1 to add, 0 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This first bit above is the plan. Notice that there are several attributes marked as &lt;code&gt;(known after apply)&lt;/code&gt;. We’ll discuss this in a minute.&lt;/p&gt;

&lt;p&gt;But first, we should ensure everything in this bucket is encrypted server-side. We’ll use &lt;code&gt;AES256&lt;/code&gt; like so:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;If you &lt;code&gt;apply&lt;/code&gt; now, you’ll see that Terraform only changes the existing bucket rather than destroying and recreating it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;**&lt;/span&gt;Plan:&lt;span class="k"&gt;**&lt;/span&gt; 0 to add, 1 to change, 0 to destroy.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But this won’t always be the case. When unsure, use &lt;code&gt;terraform plan&lt;/code&gt; to see a dry run.&lt;/p&gt;

&lt;p&gt;As an admin, you can explore this new bucket. When your done, we’ll create a user whose sole ability is to view this bucket and administer its contents.&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating a Restricted S3 bucket User
&lt;/h2&gt;

&lt;p&gt;We need to create a user and, moreover, restrict their knowledge and control to this bucket. There are many ways to do this; but for now, we’ll use an &lt;code&gt;aws_aim_user&lt;/code&gt; and an &lt;code&gt;aws_iam_user_policy&lt;/code&gt; with a separate &lt;code&gt;aws_iam_policy_document&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The User
&lt;/h3&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Nothing complex here—just a name, Alice.&lt;/p&gt;

&lt;p&gt;But to access AWS via the cli, the user will need an access key.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;br&gt;
Now we finally encounter something interesting. By &lt;code&gt;aws_iam_user.test_client.name&lt;/code&gt;, we are asking for the value of the &lt;code&gt;name&lt;/code&gt; attribute on whatever gets created by the &lt;code&gt;resource "aws_iam_access_key" "test_client"&lt;/code&gt; block. It's invaluable to note that the Terraform documentation (which I’ve been linking to as we go along) lists the attributes for each resource.&lt;br&gt;


&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;dataunbound&lt;span class="nv"&gt;$ &lt;/span&gt;terraform apply
aws_s3_bucket.test_client_bucket: Refreshing state... &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;test-client-bucket-x130099]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  &lt;span class="c"&gt;# aws_iam_access_key.test_client will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_iam_access_key"&lt;/span&gt; &lt;span class="s2"&gt;"test_client"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + encrypted_secret     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;                   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + key_fingerprint      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + secret               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;sensitive value&lt;span class="o"&gt;)&lt;/span&gt;
      + ses_smtp_password    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;sensitive value&lt;span class="o"&gt;)&lt;/span&gt;
      + ses_smtp_password_v4 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;sensitive value&lt;span class="o"&gt;)&lt;/span&gt;
      + status               &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + user                 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="c"&gt;# aws_iam_user.test_client will be created&lt;/span&gt;
  + resource &lt;span class="s2"&gt;"aws_iam_user"&lt;/span&gt; &lt;span class="s2"&gt;"test_client"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      + arn           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + force_destroy &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
      + &lt;span class="nb"&gt;id&lt;/span&gt;            &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
      + name          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"alice"&lt;/span&gt;
      + path          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
      + unique_id     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;known after apply&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only &lt;span class="s1"&gt;'yes'&lt;/span&gt; will be accepted to approve.

  Enter a value: &lt;span class="nb"&gt;yes

&lt;/span&gt;aws_iam_user.test_client: Creating...
aws_iam_user.test_client: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 1s &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;alice]
aws_iam_access_key.test_client: Creating...
aws_iam_access_key.test_client: Creation &lt;span class="nb"&gt;complete &lt;/span&gt;after 1s &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AKIA6MFJDG3VVFMKP4EF]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Even cooler, when Terraform sees this reference, it will flag it as a dependency. When we run &lt;code&gt;apply&lt;/code&gt;, resources will be created in the required order, and in parallel where possible. Here Terraform figured out it was just a literal which is why &lt;code&gt;"alice"&lt;/code&gt; was included in the plan for the access key.&lt;/p&gt;

&lt;p&gt;So we &lt;em&gt;could&lt;/em&gt; have used the literal &lt;code&gt;"alice"&lt;/code&gt; ourselves, but then we would be repeating ourselves—which is never good. But professional standards aside, you don’t always know the value of the property before it is created. Remember all the attributes in the output labeled &lt;code&gt;(known after apply)&lt;/code&gt;? Imagine, for instance, we needed the ARN rather than the name.&lt;/p&gt;

&lt;p&gt;What are the ARNS for the resources we’ve made anyway? Well, after &lt;code&gt;apply&lt;/code&gt;, you can see all current values with &lt;code&gt;terraform show&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dataunbound$ terraform  show
# aws_iam_access_key.test_client:
resource "aws_iam_access_key" "test_client" {
    id                   = "AKIA6MFJDG3VVFMKP4EF"
    secret               = (sensitive value)
    ses_smtp_password    = (sensitive value)
    ses_smtp_password_v4 = (sensitive value)
    status               = "Active"
    user                 = "alice"
}

# aws_iam_user.test_client:
resource "aws_iam_user" "test_client" {
    arn           = "arn:aws:iam::988197107435:user/alice"
    force_destroy = false
    id            = "alice"
    name          = "alice"
    path          = "/"
    unique_id     = "AIDA6MFJDG3VRPQBSAB4R"
}

# aws_s3_bucket.test_client_bucket:
resource "aws_s3_bucket" "test_client_bucket" {
    acl                         = "private"
    arn                         = "arn:aws:s3:::test-client-bucket-x130099"
    bucket                      = "test-client-bucket-x130099"
    bucket_domain_name          = "test-client-bucket-x130099.s3.amazonaws.com"
    bucket_regional_domain_name = "test-client-bucket-x130099.s3.eu-west-2.amazonaws.com"
    force_destroy               = false
    hosted_zone_id              = "Z3GKZC51ZF0DB4"
    id                          = "test-client-bucket-x130099"
    region                      = "eu-west-2"
    request_payer               = "BucketOwner"
    tags                        = {}

    server_side_encryption_configuration {
        rule {
            apply_server_side_encryption_by_default {
                sse_algorithm = "AES256"
            }
        }
    }

    versioning {
        enabled    = false
        mfa_delete = false
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Neat! It’s worth noting here that Terraform does not output-sensitive values to the screen; the &lt;code&gt;(sensitive value)&lt;/code&gt;sections are not edits on my part.&lt;/p&gt;

&lt;p&gt;But where did the access key go? And when you find it, how are you going to get it to the user? We’ll cover this in more detail in a later segment, but suffice to say look for a file in your working directory called &lt;code&gt;terraform.tfstate&lt;/code&gt;. You can find it there.&lt;/p&gt;

&lt;p&gt;Now let’s give Alice some access.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Policy
&lt;/h3&gt;

&lt;p&gt;Policies in AWS are defined as JSON. Most tutorials inline the JSON to define such policies. However, this leads to hard-coding identifiers. These can change when a resource is recreated and cause problems. I don’t know how the other authors sleep at night, but I’ll break from the norm and provide a civilized example. I’ll use a separate &lt;code&gt;aws_iam_policy_document&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;This policy allows access to the contents of &lt;code&gt;aws_s3_bucket.test_client_bucket.arn&lt;/code&gt;. This policy document is &lt;strong&gt;not&lt;/strong&gt; a resource like our other blocks. Instead, it is a &lt;a href="https://www.terraform.io/docs/configuration/data-sources.html"&gt;data source&lt;/a&gt; and will provide reusability and some protection against fat-finger mistakes.&lt;/p&gt;

&lt;p&gt;With it, we’ll create a user policy like so.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;.json&lt;/code&gt; converts our data source to literal JSON. Now let's take it for a spin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;After &lt;code&gt;apply&lt;/code&gt;, you can test your new user. I did this by adding the access key information from &lt;code&gt;terraform.tfstate&lt;/code&gt; to my &lt;code&gt;~/.aws/credentials&lt;/code&gt; file in a new section &lt;code&gt;[alice]&lt;/code&gt;. You can then call &lt;code&gt;awscli --profile alice&lt;/code&gt; followed by any of your usual commands.&lt;/p&gt;

&lt;p&gt;We may look at automated testing in a future post, but not here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps with Terraform
&lt;/h2&gt;

&lt;p&gt;Stay tuned by subscribing and you’ll get notified immediately when part II is available. In Part II we will refactor &lt;code&gt;aws-hello-world.tf&lt;/code&gt; into separate files and modules and adding optional aws console access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.freestuff.dataunbound.co.uk/nvdwpy7wjb"&gt;Download The Complete Series Example Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The post &lt;a href="https://www.dataunbound.co.uk/terraform-in-anger-part-1-aws-s3-access/"&gt;Terraform in Anger Part 1: AWS S3 Access&lt;/a&gt; appeared first on &lt;a href="https://www.dataunbound.co.uk"&gt;Data Unbound&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related posts:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/data_unbound/data-science-cloud-based-tools-for-effective-remote-collaboration-18e-temp-slug-2517700"&gt;Data Science: Cloud-based Tools for Effective Remote Collaboration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dataunbound.co.uk/data-driven-terraform-terraform-in-anger-part-2/"&gt;Data-driven Terraform: Terraform in Anger Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dataunbound.co.uk/data-engineering-what-is-it/"&gt;Data Engineering: What is it?&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>dataengineering</category>
      <category>devops</category>
      <category>aws</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
