diff --git a/Aws/DynamoDb/Core.hs b/Aws/DynamoDb/Core.hs
--- a/Aws/DynamoDb/Core.hs
+++ b/Aws/DynamoDb/Core.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP                        #-}
 {-# LANGUAGE DeriveDataTypeable         #-}
 {-# LANGUAGE FlexibleContexts           #-}
 {-# LANGUAGE FlexibleInstances          #-}
@@ -119,6 +120,9 @@
 import           Control.Applicative
 import qualified Control.Exception            as C
 import           Control.Monad
+#if MIN_VERSION_base(4,9,0)
+import qualified Control.Monad.Fail           as Fail
+#endif
 import           Control.Monad.Trans
 import           Control.Monad.Trans.Resource (throwM)
 import qualified Crypto.Hash                  as CH
@@ -1250,9 +1254,17 @@
     {-# INLINE (>>=) #-}
     return a = Parser $ \_kf ks -> ks a
     {-# INLINE return #-}
+#if !(MIN_VERSION_base(4,13,0))
     fail msg = Parser $ \kf _ks -> kf msg
     {-# INLINE fail #-}
+#endif
 
+#if MIN_VERSION_base(4,9,0)
+instance Fail.MonadFail Parser where
+    fail msg = Parser $ \kf _ks -> kf msg
+    {-# INLINE fail #-}
+#endif
+
 instance Functor Parser where
     fmap f m = Parser $ \kf ks -> let ks' a = ks (f a)
                                   in runParser m kf ks'
@@ -1382,7 +1394,7 @@
 parseAttr k m =
   case M.lookup k m of
     Nothing -> fail ("Key " Sem.<> T.unpack k Sem.<> " not found")
-    Just (DMap dv) -> either (fail "...") return $ fromItem dv
+    Just (DMap dv) -> either (const (fail "...")) return $ fromItem dv
     _       -> fail ("Key " Sem.<> T.unpack k Sem.<> " is not a map!")
 
 -------------------------------------------------------------------------------
diff --git a/Aws/Iam/Commands.hs b/Aws/Iam/Commands.hs
--- a/Aws/Iam/Commands.hs
+++ b/Aws/Iam/Commands.hs
@@ -1,31 +1,51 @@
 module Aws.Iam.Commands
-    ( module Aws.Iam.Commands.CreateAccessKey
+    ( module Aws.Iam.Commands.AddUserToGroup
+    , module Aws.Iam.Commands.CreateAccessKey
+    , module Aws.Iam.Commands.CreateGroup
     , module Aws.Iam.Commands.CreateUser
     , module Aws.Iam.Commands.DeleteAccessKey
+    , module Aws.Iam.Commands.DeleteGroup
+    , module Aws.Iam.Commands.DeleteGroupPolicy
     , module Aws.Iam.Commands.DeleteUser
     , module Aws.Iam.Commands.DeleteUserPolicy
+    , module Aws.Iam.Commands.GetGroupPolicy
     , module Aws.Iam.Commands.GetUser
     , module Aws.Iam.Commands.GetUserPolicy
     , module Aws.Iam.Commands.ListAccessKeys
     , module Aws.Iam.Commands.ListMfaDevices
+    , module Aws.Iam.Commands.ListGroupPolicies
+    , module Aws.Iam.Commands.ListGroups
     , module Aws.Iam.Commands.ListUserPolicies
     , module Aws.Iam.Commands.ListUsers
+    , module Aws.Iam.Commands.PutGroupPolicy
     , module Aws.Iam.Commands.PutUserPolicy
+    , module Aws.Iam.Commands.RemoveUserFromGroup
     , module Aws.Iam.Commands.UpdateAccessKey
+    , module Aws.Iam.Commands.UpdateGroup
     , module Aws.Iam.Commands.UpdateUser
     ) where
 
+import           Aws.Iam.Commands.AddUserToGroup
 import           Aws.Iam.Commands.CreateAccessKey
+import           Aws.Iam.Commands.CreateGroup
 import           Aws.Iam.Commands.CreateUser
 import           Aws.Iam.Commands.DeleteAccessKey
+import           Aws.Iam.Commands.DeleteGroup
+import           Aws.Iam.Commands.DeleteGroupPolicy
 import           Aws.Iam.Commands.DeleteUser
 import           Aws.Iam.Commands.DeleteUserPolicy
+import           Aws.Iam.Commands.GetGroupPolicy
 import           Aws.Iam.Commands.GetUser
 import           Aws.Iam.Commands.GetUserPolicy
 import           Aws.Iam.Commands.ListAccessKeys
 import           Aws.Iam.Commands.ListMfaDevices
+import           Aws.Iam.Commands.ListGroupPolicies
+import           Aws.Iam.Commands.ListGroups
 import           Aws.Iam.Commands.ListUserPolicies
 import           Aws.Iam.Commands.ListUsers
+import           Aws.Iam.Commands.PutGroupPolicy
 import           Aws.Iam.Commands.PutUserPolicy
+import           Aws.Iam.Commands.RemoveUserFromGroup
 import           Aws.Iam.Commands.UpdateAccessKey
+import           Aws.Iam.Commands.UpdateGroup
 import           Aws.Iam.Commands.UpdateUser
diff --git a/Aws/Iam/Commands/AddUserToGroup.hs b/Aws/Iam/Commands/AddUserToGroup.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/AddUserToGroup.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.AddUserToGroup
+    ( AddUserToGroup(..)
+    , AddUserToGroupResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text        (Text)
+import           Data.Typeable
+
+-- | Adds the specified user to the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_AddUserToGroup.html>
+data AddUserToGroup
+    = AddUserToGroup {
+        autgGroupName :: Text
+      -- ^ Name of the group to update.
+      , autgUserName  :: Text
+      -- ^ The of the user to add.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery AddUserToGroup where
+    type ServiceConfiguration AddUserToGroup = IamConfiguration
+    signQuery AddUserToGroup{..}
+        = iamAction "AddUserToGroup" [
+              ("GroupName"     , autgGroupName)
+            , ("UserName"      , autgUserName)
+            ]
+
+data AddUserToGroupResponse = AddUserToGroupResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer AddUserToGroup AddUserToGroupResponse where
+    type ResponseMetadata AddUserToGroupResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer (const $ return AddUserToGroupResponse)
+
+instance Transaction AddUserToGroup AddUserToGroupResponse
+
+instance AsMemoryResponse AddUserToGroupResponse where
+    type MemoryResponse AddUserToGroupResponse = AddUserToGroupResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/CreateGroup.hs b/Aws/Iam/Commands/CreateGroup.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/CreateGroup.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TupleSections         #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.CreateGroup
+    ( CreateGroup(..)
+    , CreateGroupResponse(..)
+    , Group(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Control.Applicative
+import           Data.Text           (Text)
+import           Data.Typeable
+import           Prelude
+
+-- | Creates a new group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateGroup.html>
+data CreateGroup
+    = CreateGroup {
+        cgGroupName :: Text
+      -- ^ Name of the new group
+      , cgPath     :: Maybe Text
+      -- ^ Path under which the group will be created. Defaults to @/@ if
+      -- omitted.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery CreateGroup where
+    type ServiceConfiguration CreateGroup = IamConfiguration
+    signQuery CreateGroup{..}
+        = iamAction' "CreateGroup" [
+              Just ("GroupName", cgGroupName)
+            , ("Path",) <$> cgPath
+            ]
+
+data CreateGroupResponse = CreateGroupResponse Group
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer CreateGroup CreateGroupResponse where
+    type ResponseMetadata CreateGroupResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer $
+          fmap CreateGroupResponse . parseGroup
+
+instance Transaction CreateGroup CreateGroupResponse
+
+instance AsMemoryResponse CreateGroupResponse where
+    type MemoryResponse CreateGroupResponse = CreateGroupResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/DeleteGroup.hs b/Aws/Iam/Commands/DeleteGroup.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/DeleteGroup.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.DeleteGroup
+    ( DeleteGroup(..)
+    , DeleteGroupResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text          (Text)
+import           Data.Typeable
+
+-- | Deletes the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteGroup.html>
+data DeleteGroup = DeleteGroup Text
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery DeleteGroup where
+    type ServiceConfiguration DeleteGroup = IamConfiguration
+    signQuery (DeleteGroup groupName)
+        = iamAction "DeleteGroup" [("GroupName", groupName)]
+
+data DeleteGroupResponse = DeleteGroupResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer DeleteGroup DeleteGroupResponse where
+    type ResponseMetadata DeleteGroupResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer (const $ return DeleteGroupResponse)
+
+instance Transaction DeleteGroup DeleteGroupResponse
+
+instance AsMemoryResponse DeleteGroupResponse where
+    type MemoryResponse DeleteGroupResponse = DeleteGroupResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/DeleteGroupPolicy.hs b/Aws/Iam/Commands/DeleteGroupPolicy.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/DeleteGroupPolicy.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.DeleteGroupPolicy
+    ( DeleteGroupPolicy(..)
+    , DeleteGroupPolicyResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text          (Text)
+import           Data.Typeable
+
+-- | Deletes the specified policy associated with the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteGroupPolicy.html>
+data DeleteGroupPolicy
+    = DeleteGroupPolicy {
+        dgpPolicyName :: Text
+      -- ^ Name of the policy to be deleted.
+      , dgpGroupName   :: Text
+      -- ^ Name of the group with whom the policy is associated.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery DeleteGroupPolicy where
+    type ServiceConfiguration DeleteGroupPolicy = IamConfiguration
+    signQuery DeleteGroupPolicy{..}
+        = iamAction "DeleteGroupPolicy" [
+              ("PolicyName", dgpPolicyName)
+            , ("GroupName", dgpGroupName)
+            ]
+
+data DeleteGroupPolicyResponse = DeleteGroupPolicyResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer DeleteGroupPolicy DeleteGroupPolicyResponse where
+    type ResponseMetadata DeleteGroupPolicyResponse = IamMetadata
+    responseConsumer _ _ =
+        iamResponseConsumer (const $ return DeleteGroupPolicyResponse)
+
+instance Transaction DeleteGroupPolicy DeleteGroupPolicyResponse
+
+instance AsMemoryResponse DeleteGroupPolicyResponse where
+    type MemoryResponse DeleteGroupPolicyResponse = DeleteGroupPolicyResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/GetGroupPolicy.hs b/Aws/Iam/Commands/GetGroupPolicy.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/GetGroupPolicy.hs
@@ -0,0 +1,72 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.GetGroupPolicy
+    ( GetGroupPolicy(..)
+    , GetGroupPolicyResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Control.Applicative
+import           Data.Text           (Text)
+import qualified Data.Text           as Text
+import qualified Data.Text.Encoding  as Text
+import           Data.Typeable
+import qualified Network.HTTP.Types  as HTTP
+import           Text.XML.Cursor     (($//))
+import           Prelude
+
+-- | Retreives the specified policy document for the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_GetGroupPolicy.html>
+data GetGroupPolicy
+    = GetGroupPolicy {
+        ggpPolicyName :: Text
+      -- ^ Name of the policy.
+      , ggpGroupName   :: Text
+      -- ^ Name of the group with whom the policy is associated.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery GetGroupPolicy where
+    type ServiceConfiguration GetGroupPolicy = IamConfiguration
+    signQuery GetGroupPolicy{..}
+        = iamAction "GetGroupPolicy" [
+              ("PolicyName", ggpPolicyName)
+            , ("GroupName", ggpGroupName)
+            ]
+
+data GetGroupPolicyResponse
+    = GetGroupPolicyResponse {
+        ggprPolicyDocument :: Text
+      -- ^ The policy document.
+      , ggprPolicyName     :: Text
+      -- ^ Name of the policy.
+      , ggprGroupName       :: Text
+      -- ^ Name of the group with whom the policy is associated.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer GetGroupPolicy GetGroupPolicyResponse where
+    type ResponseMetadata GetGroupPolicyResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer $ \cursor -> do
+            let attr name = force ("Missing " ++ Text.unpack name) $
+                            cursor $// elContent name
+            ggprPolicyDocument <- decodePolicy <$>
+                                  attr "PolicyDocument"
+            ggprPolicyName     <- attr "PolicyName"
+            ggprGroupName       <- attr "GroupName"
+            return GetGroupPolicyResponse{..}
+        where
+          decodePolicy = Text.decodeUtf8 . HTTP.urlDecode False
+                       . Text.encodeUtf8
+
+
+instance Transaction GetGroupPolicy GetGroupPolicyResponse
+
+instance AsMemoryResponse GetGroupPolicyResponse where
+    type MemoryResponse GetGroupPolicyResponse = GetGroupPolicyResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/ListGroupPolicies.hs b/Aws/Iam/Commands/ListGroupPolicies.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/ListGroupPolicies.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TupleSections         #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.ListGroupPolicies
+    ( ListGroupPolicies(..)
+    , ListGroupPoliciesResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text        (Text)
+import           Data.Typeable
+import           Text.XML.Cursor  (content, laxElement, ($//), (&/))
+
+-- | Lists the group policies associated with the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_ListGroupPolicies.html>
+data ListGroupPolicies
+    = ListGroupPolicies {
+        lgpGroupName :: Text
+      -- ^ Policies associated with this group will be listed.
+      , lgpMarker   :: Maybe Text
+      -- ^ Used for paginating requests. Marks the position of the last
+      -- request.
+      , lgpMaxItems :: Maybe Integer
+      -- ^ Used for paginating requests. Specifies the maximum number of items
+      -- to return in the response. Defaults to 100.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery ListGroupPolicies where
+    type ServiceConfiguration ListGroupPolicies = IamConfiguration
+    signQuery ListGroupPolicies{..}
+        = iamAction' "ListGroupPolicies" $ [
+              Just ("GroupName", lgpGroupName)
+            ] <> markedIter lgpMarker lgpMaxItems
+
+data ListGroupPoliciesResponse
+    = ListGroupPoliciesResponse {
+        lgprPolicyNames :: [Text]
+      -- ^ List of policy names.
+      , lgprIsTruncated :: Bool
+      -- ^ @True@ if the request was truncated because of too many items.
+      , lgprMarker      :: Maybe Text
+      -- ^ Marks the position at which the request was truncated. This value
+      -- must be passed with the next request to continue listing from the
+      -- last position.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer ListGroupPolicies ListGroupPoliciesResponse where
+    type ResponseMetadata ListGroupPoliciesResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer $ \cursor -> do
+            (lgprIsTruncated, lgprMarker) <- markedIterResponse cursor
+            let lgprPolicyNames = cursor $// laxElement "member" &/ content
+            return ListGroupPoliciesResponse{..}
+
+instance Transaction ListGroupPolicies ListGroupPoliciesResponse
+
+instance IteratedTransaction ListGroupPolicies ListGroupPoliciesResponse where
+    nextIteratedRequest request response
+        = case lgprMarker response of
+            Nothing     -> Nothing
+            Just marker -> Just $ request { lgpMarker = Just marker }
+
+instance AsMemoryResponse ListGroupPoliciesResponse where
+    type MemoryResponse ListGroupPoliciesResponse = ListGroupPoliciesResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/ListGroups.hs b/Aws/Iam/Commands/ListGroups.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/ListGroups.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TupleSections         #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.ListGroups
+    ( ListGroups(..)
+    , ListGroupsResponse(..)
+    , Group(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Control.Applicative
+import           Data.Text           (Text)
+import           Data.Typeable
+import           Prelude
+import           Text.XML.Cursor     (laxElement, ($//), (&|))
+
+-- | Lists groups that have the specified path prefix.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_ListGroups.html>
+data ListGroups
+    = ListGroups {
+        lgPathPrefix :: Maybe Text
+      -- ^ Groups defined under this path will be listed. If omitted, defaults
+      -- to @/@, which lists all groups.
+      , lgMarker     :: Maybe Text
+      -- ^ Used for paginating requests. Marks the position of the last
+      -- request.
+      , lgMaxItems   :: Maybe Integer
+      -- ^ Used for paginating requests. Specifies the maximum number of items
+      -- to return in the response. Defaults to 100.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery ListGroups where
+    type ServiceConfiguration ListGroups = IamConfiguration
+    signQuery ListGroups{..}
+        = iamAction' "ListGroups" $ [
+              ("PathPrefix",) <$> lgPathPrefix
+            ] <> markedIter lgMarker lgMaxItems
+
+data ListGroupsResponse
+    = ListGroupsResponse {
+        lgrGroups       :: [Group]
+      -- ^ List of 'Group's.
+      , lgrIsTruncated :: Bool
+      -- ^ @True@ if the request was truncated because of too many items.
+      , lgrMarker      :: Maybe Text
+      -- ^ Marks the position at which the request was truncated. This value
+      -- must be passed with the next request to continue listing from the
+      -- last position.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer ListGroups ListGroupsResponse where
+    type ResponseMetadata ListGroupsResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer $ \cursor -> do
+            (lgrIsTruncated, lgrMarker) <- markedIterResponse cursor
+            lgrGroups <- sequence $
+                cursor $// laxElement "member" &| parseGroup
+            return ListGroupsResponse{..}
+
+instance Transaction ListGroups ListGroupsResponse
+
+instance IteratedTransaction ListGroups ListGroupsResponse where
+    nextIteratedRequest request response
+        = case lgrMarker response of
+            Nothing     -> Nothing
+            Just marker -> Just $ request { lgMarker = Just marker }
+
+instance AsMemoryResponse ListGroupsResponse where
+    type MemoryResponse ListGroupsResponse = ListGroupsResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/PutGroupPolicy.hs b/Aws/Iam/Commands/PutGroupPolicy.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/PutGroupPolicy.hs
@@ -0,0 +1,51 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.PutGroupPolicy
+    ( PutGroupPolicy(..)
+    , PutGroupPolicyResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text        (Text)
+import           Data.Typeable
+
+-- | Adds a policy document with the specified name, associated with the
+-- specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_PutGroupPolicy.html>
+data PutGroupPolicy
+    = PutGroupPolicy {
+        pgpPolicyDocument :: Text
+      -- ^ The policy document.
+      , pgpPolicyName     :: Text
+      -- ^ Name of the policy.
+      , pgpGroupName       :: Text
+      -- ^ Name of the group with whom this policy is associated.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery PutGroupPolicy where
+    type ServiceConfiguration PutGroupPolicy = IamConfiguration
+    signQuery PutGroupPolicy{..}
+        = iamAction "PutGroupPolicy" [
+              ("PolicyDocument", pgpPolicyDocument)
+            , ("PolicyName"    , pgpPolicyName)
+            , ("GroupName"      , pgpGroupName)
+            ]
+
+data PutGroupPolicyResponse = PutGroupPolicyResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer PutGroupPolicy PutGroupPolicyResponse where
+    type ResponseMetadata PutGroupPolicyResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer (const $ return PutGroupPolicyResponse)
+
+instance Transaction PutGroupPolicy PutGroupPolicyResponse
+
+instance AsMemoryResponse PutGroupPolicyResponse where
+    type MemoryResponse PutGroupPolicyResponse = PutGroupPolicyResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/RemoveUserFromGroup.hs b/Aws/Iam/Commands/RemoveUserFromGroup.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/RemoveUserFromGroup.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.RemoveUserFromGroup
+    ( RemoveUserFromGroup(..)
+    , RemoveUserFromGroupResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Data.Text        (Text)
+import           Data.Typeable
+
+-- | Removes the specified user from the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_RemoveUserFromGroup.html>
+data RemoveUserFromGroup
+    = RemoveUserFromGroup {
+        rufgGroupName :: Text
+      -- ^ Name of the group to update.
+      , rufgUserName  :: Text
+      -- ^ The of the user to add.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery RemoveUserFromGroup where
+    type ServiceConfiguration RemoveUserFromGroup = IamConfiguration
+    signQuery RemoveUserFromGroup{..}
+        = iamAction "RemoveUserFromGroup" [
+              ("GroupName"     , rufgGroupName)
+            , ("UserName"      , rufgUserName)
+            ]
+
+data RemoveUserFromGroupResponse = RemoveUserFromGroupResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer RemoveUserFromGroup RemoveUserFromGroupResponse where
+    type ResponseMetadata RemoveUserFromGroupResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer (const $ return RemoveUserFromGroupResponse)
+
+instance Transaction RemoveUserFromGroup RemoveUserFromGroupResponse
+
+instance AsMemoryResponse RemoveUserFromGroupResponse where
+    type MemoryResponse RemoveUserFromGroupResponse = RemoveUserFromGroupResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Commands/UpdateGroup.hs b/Aws/Iam/Commands/UpdateGroup.hs
new file mode 100644
--- /dev/null
+++ b/Aws/Iam/Commands/UpdateGroup.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards       #-}
+{-# LANGUAGE TupleSections         #-}
+{-# LANGUAGE TypeFamilies          #-}
+module Aws.Iam.Commands.UpdateGroup
+    ( UpdateGroup(..)
+    , UpdateGroupResponse(..)
+    ) where
+
+import           Aws.Core
+import           Aws.Iam.Core
+import           Aws.Iam.Internal
+import           Control.Applicative
+import           Data.Text           (Text)
+import           Data.Typeable
+import           Prelude
+
+-- | Updates the name and/or path of the specified group.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateGroup.html>
+data UpdateGroup
+    = UpdateGroup {
+        ugGroupName    :: Text
+      -- ^ Name of the group to be updated.
+      , ugNewGroupName :: Maybe Text
+      -- ^ New name for the group.
+      , ugNewPath     :: Maybe Text
+      -- ^ New path to which the group will be moved.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+instance SignQuery UpdateGroup where
+    type ServiceConfiguration UpdateGroup = IamConfiguration
+    signQuery UpdateGroup{..}
+        = iamAction' "UpdateGroup" [
+              Just ("GroupName", ugGroupName)
+            , ("NewGroupName",) <$> ugNewGroupName
+            , ("NewPath",) <$> ugNewPath
+            ]
+
+data UpdateGroupResponse = UpdateGroupResponse
+    deriving (Eq, Ord, Show, Typeable)
+
+instance ResponseConsumer UpdateGroup UpdateGroupResponse where
+    type ResponseMetadata UpdateGroupResponse = IamMetadata
+    responseConsumer _ _
+        = iamResponseConsumer (const $ return UpdateGroupResponse)
+
+instance Transaction UpdateGroup UpdateGroupResponse
+
+instance AsMemoryResponse UpdateGroupResponse where
+    type MemoryResponse UpdateGroupResponse = UpdateGroupResponse
+    loadToMemory = return
diff --git a/Aws/Iam/Core.hs b/Aws/Iam/Core.hs
--- a/Aws/Iam/Core.hs
+++ b/Aws/Iam/Core.hs
@@ -14,6 +14,8 @@
     , AccessKeyStatus(..)
     , User(..)
     , parseUser
+    , Group(..)
+    , parseGroup
     , MfaDevice(..)
     , parseMfaDevice
     ) where
@@ -197,6 +199,38 @@
     userUserId     <- attr "UserId"
     userUserName   <- attr "UserName"
     return User{..}
+  where
+    attr name = force ("Missing " ++ Text.unpack name) $
+                cursor $// elContent name
+
+
+-- | The IAM @Group@ data type.
+--
+-- <http://docs.aws.amazon.com/IAM/latest/APIReference/API_Group.html>
+data Group
+    = Group {
+        groupArn        :: Text
+      -- ^ ARN used to refer to this group.
+      , groupCreateDate :: UTCTime
+      -- ^ Date and time at which the group was created.
+      , groupPath       :: Text
+      -- ^ Path under which the group was created.
+      , groupGroupId     :: Text
+      -- ^ Unique identifier used to refer to this group. 
+      , groupGroupName   :: Text
+      -- ^ Name of the group.
+      }
+    deriving (Eq, Ord, Show, Typeable)
+
+-- | Parses the IAM @Group@ data type.
+parseGroup :: MonadThrow m => Cu.Cursor -> m Group
+parseGroup cursor = do
+    groupArn        <- attr "Arn"
+    groupCreateDate <- attr "CreateDate" >>= parseDateTime . Text.unpack
+    groupPath       <- attr "Path"
+    groupGroupId     <- attr "GroupId"
+    groupGroupName   <- attr "GroupName"
+    return Group{..}
   where
     attr name = force ("Missing " ++ Text.unpack name) $
                 cursor $// elContent name
diff --git a/Aws/S3/Commands/PutObject.hs b/Aws/S3/Commands/PutObject.hs
--- a/Aws/S3/Commands/PutObject.hs
+++ b/Aws/S3/Commands/PutObject.hs
@@ -39,9 +39,10 @@
 putObject bucket obj body = PutObject obj bucket Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing body [] False False
 
 data PutObjectResponse
-  = PutObjectResponse {
-      porVersionId :: Maybe T.Text
-    }
+  = PutObjectResponse
+      { porVersionId :: Maybe T.Text
+      , porETag :: T.Text
+      }
   deriving (Show)
 
 -- | ServiceConfiguration: 'S3Configuration'
@@ -78,7 +79,8 @@
     type ResponseMetadata PutObjectResponse = S3Metadata
     responseConsumer _ _ = s3ResponseConsumer $ \resp -> do
       let vid = T.decodeUtf8 `fmap` lookup "x-amz-version-id" (HTTP.responseHeaders resp)
-      return $ PutObjectResponse vid
+      let etag = fromMaybe "" $ T.decodeUtf8 `fmap` lookup "ETag" (HTTP.responseHeaders resp)
+      return $ PutObjectResponse vid etag
 
 instance Transaction PutObject PutObjectResponse
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+0.22 series
+-----------
+
+-   0.22
+    - Support GHC 8.8
+    - Support network-3
+    - Support http-client 0.6+
+    - S3: add etag to PutObjectResponse
+    - Add IAM group manipulation methods
+
 0.21 series
 -----------
 
diff --git a/aws.cabal b/aws.cabal
--- a/aws.cabal
+++ b/aws.cabal
@@ -1,5 +1,5 @@
 Name:                aws
-Version:             0.21.1
+Version:             0.22
 Synopsis:            Amazon Web Services (AWS) for Haskell
 Description:         Bindings for Amazon Web Services (AWS), with the aim of supporting all AWS services. To see a high level overview of the library, see the README at <https://github.com/aristidb/aws/blob/master/README.md>.
 Homepage:            http://github.com/aristidb/aws
@@ -19,7 +19,7 @@
 Source-repository this
   type: git
   location: https://github.com/aristidb/aws.git
-  tag: 0.21.1
+  tag: 0.22
 
 Source-repository head
   type: git
@@ -29,6 +29,10 @@
   Description: Build the examples.
   Default: False
 
+Flag NetworkBSD
+  Description: Build with network-3.0 which split out network-bsd
+  Default: True
+
 Library
   Exposed-modules:
                        Aws
@@ -49,19 +53,29 @@
                        Aws.Ec2.InstanceMetadata
                        Aws.Iam
                        Aws.Iam.Commands
+                       Aws.Iam.Commands.AddUserToGroup
                        Aws.Iam.Commands.CreateAccessKey
+                       Aws.Iam.Commands.CreateGroup
                        Aws.Iam.Commands.CreateUser
                        Aws.Iam.Commands.DeleteAccessKey
+                       Aws.Iam.Commands.DeleteGroup
+                       Aws.Iam.Commands.DeleteGroupPolicy
                        Aws.Iam.Commands.DeleteUser
                        Aws.Iam.Commands.DeleteUserPolicy
+                       Aws.Iam.Commands.GetGroupPolicy
                        Aws.Iam.Commands.GetUser
                        Aws.Iam.Commands.GetUserPolicy
                        Aws.Iam.Commands.ListAccessKeys
                        Aws.Iam.Commands.ListMfaDevices
+                       Aws.Iam.Commands.ListGroupPolicies
+                       Aws.Iam.Commands.ListGroups
                        Aws.Iam.Commands.ListUserPolicies
                        Aws.Iam.Commands.ListUsers
+                       Aws.Iam.Commands.PutGroupPolicy
                        Aws.Iam.Commands.PutUserPolicy
+                       Aws.Iam.Commands.RemoveUserFromGroup
                        Aws.Iam.Commands.UpdateAccessKey
+                       Aws.Iam.Commands.UpdateGroup
                        Aws.Iam.Commands.UpdateUser
                        Aws.Iam.Core
                        Aws.Iam.Internal
@@ -139,7 +153,6 @@
                        monad-control        >= 0.3,
                        exceptions           >= 0.8     && < 0.11,
                        mtl                  == 2.*,
-                       network              == 2.*,
                        old-locale           == 1.*,
                        resourcet            >= 1.2     && < 1.3,
                        safe                 >= 0.3     && < 0.4,
@@ -152,6 +165,10 @@
                        utf8-string          >= 0.3     && < 1.1,
                        vector               >= 0.10,
                        xml-conduit          >= 1.8     && <2.0
+  if flag(NetworkBSD)
+    Build-depends: network == 3.*, network-bsd == 2.8.*
+  else
+    Build-depends: network == 2.*
  
   if !impl(ghc >= 7.6)
     Build-depends: ghc-prim
@@ -379,7 +396,7 @@
         base == 4.*,
         bytestring >= 0.10,
         errors >= 2.0,
-        http-client >= 0.3 && < 0.6,
+        http-client >= 0.3 && < 0.7,
         lifted-base >= 0.2,
         monad-control >= 0.3,
         mtl >= 2.1,
@@ -447,7 +464,7 @@
         lifted-base >= 0.2,
         monad-control >= 0.3,
         mtl >= 2.1,
-        http-client < 0.6,
+        http-client < 0.7,
         http-client-tls < 0.5,
         http-types,
         resourcet,
